@SpringBootTest vs @DataJpaTest vs @WebMvcTest, how slices auto-configure, what happens when @SpringBootTest loads the context (and context caching), testing one layer in isolation, @TestConfiguration, @DirtiesContext, test transactions and preventing rollback, unit vs integration tests, webEnvironment modes, testing service layers, conditional/profile beans, controlling properties, resetting database state, parallel integration tests, embedded H2, and injecting security credentials.
Published September 25, 2026
A senior test strategy balances confidence, speed and maintainability:
Know how Spring's context caching works, because most slow test suites come from breaking it.
@SpringBootTest, @DataJpaTest and @WebMvcTest? When would you use @WebMvcTest instead of @SpringBootTest?Short answer:
@SpringBootTest: loads the full application context (every bean and auto-configuration). It's optionally with a real server (webEnvironment). It's for integration tests of whole flows. It's the slowest.@WebMvcTest(Controller.class): only the MVC slice: the controllers, @ControllerAdvice, filters, converters, WebMvcConfigurers and Spring Security's web configuration, with MockMvc auto-configured. No services or repositories: mock them with @MockitoBean. It's fast, and focused on request mapping, validation, serialisation, status codes and security rules.@DataJpaTest: only the JPA slice: entities, repositories, EntityManager, DataSource, Flyway or Liquibase, and transactional tests that roll back by default. It replaces the DataSource with an embedded database, unless @AutoConfigureTestDatabase(replace = NONE) (with Testcontainers). It's for repository queries and mappings.Use @WebMvcTest when you're testing the web contract and don't need the business logic running. It starts much faster, and failures are localised. Use @SpringBootTest for cross-layer behaviour (a transaction plus events plus the web layer).
Learn it in depth → Global Exception Handling
@DataJpaTest and @DataMongoTest?Short answer: Each slice annotation is a meta-annotation combining:
@BootstrapWith (a slice-specific context bootstrapper);@TypeExcludeFilters, which limits component scanning to the relevant stereotypes (for example, only @Repository, or only @Controller/@ControllerAdvice);@ImportAutoConfiguration, which loads only the auto-configurations listed for that slice in META-INF/spring/org.springframework.boot.test.autoconfigure.orm.jpa.AutoConfigureDataJpa.imports (and similar files);@Transactional, @AutoConfigureTestDatabase, TestEntityManager, MockMvc).So a @DataMongoTest gets the Mongo template and repositories (and, with Testcontainers, @ServiceConnection), but no web layer. You can add beans back with @Import or @TestConfiguration, and write your own slices the same way.
@SpringBootTest loads the context? How does context caching work?Short answer:
SpringBootTestContextBootstrapper finds the @SpringBootConfiguration (by searching the test's package upwards), and builds a MergedContextConfiguration: the configuration classes, active profiles, property sources (properties, @TestPropertySource, @DynamicPropertySource), context customisers (mock beans, @ServiceConnection) and web environment.SpringApplication (so the environment, auto-configuration and listeners are real), with the test-specific customisers applied.MergedContextConfiguration. Other test classes with an identical configuration reuse it, which is huge for speed.@DirtiesContext, or a different key (a different set of @MockitoBeans, properties or profiles), forces a new context. The cache is bounded (default 32 entries, with LRU eviction).webEnvironment = RANDOM_PORT starts a real embedded server.Speed tip: standardise the test configuration (a shared abstract base class, or a meta-annotation) so the tests share one cached context.
@SpringBootTest(webEnvironment = …) actually do?Short answer:
MOCK (the default): a mock servlet environment, with no real server. Use it with @AutoConfigureMockMvc and MockMvc. It's fast. The request goes through the DispatcherServlet and filters, but not the network or the servlet container.RANDOM_PORT: starts the real embedded server on a random port. Inject it with @LocalServerPort, and call it with TestRestTemplate/WebTestClient/RestClient. It tests the real HTTP stack (the container's filters, error pages, compression, TLS).DEFINED_PORT: a real server on the configured port (conflict-prone in CI).NONE: no web environment at all (batch jobs, messaging-only applications).Common trap: with a real server, the test and the server run on different threads, so @Transactional test rollback doesn't cover the server-side work. Clean the data up explicitly.
Short answer:
@WebMvcTest(OrderController.class), with the services as @MockitoBean.@DataJpaTest (with Testcontainers for the real database).@SpringBootTest(webEnvironment = NONE), or a custom slice (@DataJpaTest + @Import(OrderService.class, …)) with Testcontainers, mocking only external clients (@MockitoBean PaymentClient), or stubbing them with WireMock.@TestConfiguration?Short answer: To add or override beans for tests only, without polluting production configuration or component scanning:
Clock (deterministic time);ObjectMapper or security settings;@ServiceConnection (Boot 3.1+ TestcontainersConfiguration).As a nested static class, it's picked up automatically by the enclosing test. As a top-level class, import it with @Import. It's excluded from component scanning by default (through TypeExcludeFilter).
@DirtiesContext? When should you avoid it?Short answer: @DirtiesContext marks the application context as dirty, so Spring closes it and removes it from the cache after the test method or class (the classMode/methodMode options). Use it when a test mutates shared context state that can't easily be reset:
Avoid it in most cases: it destroys context caching, so every dirtied test pays full context start-up, and the suites get dramatically slower. Prefer:
@BeforeEach/@AfterEach cleanup, @Sql scripts, truncating tables);@MockitoBean, which resets its mocks automatically after each test.@Transactional do in test classes? How do you prevent rollback (persist data) during a test? How do you reset database state between tests?Short answer:
@Transactional on a test (or the class) makes the Spring TestContext framework start a transaction before each test method, and roll it back afterwards (@DataJpaTest does this by default). The database stays clean, with no manual cleanup.@Commit or @Rollback(false). That's useful for debugging, or for verifying behaviour after commit (for example AFTER_COMMIT listeners).@TransactionalEventListener(AFTER_COMMIT) listeners don't run, deferred constraints aren't checked, and flush-time errors may never happen (call flush(), or test without a transaction);@Async, or a real server with RANDOM_PORT);@Sql scripts (before or after the test method: truncate, insert fixtures);deleteFromTables;clean + migrate per test class (slow but thorough);Short answer:
Aim for the testing pyramid (or "honeycomb" for microservices: more integration tests around the service boundaries).
Short answer:
@ActiveProfiles("prod") in a context test, and assert on the beans present or absent (assertThat(context).hasSingleBean(...)/doesNotHaveBean(...)).@Conditional* logic: ApplicationContextRunner (or WebApplicationContextRunner). It's fast, with no Spring Boot startup overhead, and you declare properties, user configurations and classpath filters per case:private final ApplicationContextRunner runner = new ApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(AuditAutoConfiguration.class));
@Test void backsOffWhenUserDefinesClient() {
runner.withUserConfiguration(CustomClientConfig.class)
.run(ctx -> assertThat(ctx).hasSingleBean(AuditClient.class).getBean(AuditClient.class).isSameAs(CustomClientConfig.CLIENT));
}
@Test void disabledByProperty() {
runner.withPropertyValues("acme.audit.enabled=false").run(ctx -> assertThat(ctx).doesNotHaveBean(AuditClient.class));
}
@Test void absentWithoutLibrary() {
runner.withClassLoader(new FilteredClassLoader(AuditClient.class)).run(ctx -> assertThat(ctx).doesNotHaveBean(AuditAutoConfiguration.class));
}
Short answer:
@SpringBootTest(properties = {"app.feature.x=true"}), or @TestPropertySource (inline properties, or a file). These have high precedence.@DynamicPropertySource: properties computed at runtime (Testcontainers ports and URLs, WireMock base URLs). Largely replaced by @ServiceConnection in Boot 3.1+.application-test.yml with @ActiveProfiles("test").Keep each test's properties explicit, and minimal. Every unique property combination creates a separate cached context.
Short answer: Yes, with care. JUnit 5 supports parallel execution (junit.jupiter.execution.parallel.enabled=true, and mode.default=concurrent). Build tools can also fork JVMs (Maven Surefire forkCount, Gradle maxParallelForks). The risks come from shared state:
Strategy: run forks in parallel, with isolated infrastructure per fork, and keep tests within a context sequential unless they're proven independent. Use @ResourceLock/@Isolated in JUnit for the exceptions.
Short answer: @DataJpaTest (by default) or @AutoConfigureTestDatabase replaces the DataSource with an embedded H2/HSQL/Derby instance, when it's on the classpath. Hibernate creates the schema (or Flyway runs). H2's compatibility modes (MODE=PostgreSQL) help a little. The problems:
Prefer Testcontainers with the same database engine and version as production. Keep H2 only for very simple, portable repository tests, or where Docker isn't available.
Short answer:
@WithMockUser(username = "alice", roles = "ADMIN"), @WithUserDetails, or custom @WithSecurityContext annotations;.with(jwt().jwt(j -> j.claim("scope", "orders.read")).authorities(...)), .with(oauth2Login()), .with(user("alice").roles("USER")), and .with(csrf()) for form or cookie flows.RANDOM_PORT): obtain a real token:
mock-oauth2-server);spring.security.oauth2.resourceserver.jwt.public-key-location).Authorization: Bearer ….WebTestClient with mutateWith(mockJwt()).Never disable security for the whole test suite. Test the authorisation rules explicitly (401, 403, and allowed roles).
Q: How many cached contexts are too many? A: Each context holds its beans, pools and threads in memory, and starting one can take seconds. If the logs show many context starts (look for "Started … in X seconds" repeatedly), consolidate the configurations. Spring 6.2 can pause inactive cached contexts, to reduce resource use.
Q: @MockitoBean vs @MockBean?
A: @MockBean (Spring Boot) is deprecated since 3.4 in favour of @MockitoBean/@MockitoSpyBean (Spring Framework 6.2), which live in the core test context framework and support fields in @Nested classes and configurations more consistently.
Q: What is TestEntityManager?
A: A test helper in @DataJpaTest that wraps EntityManager with convenient methods (persistAndFlush, find), for setting up data and forcing flushes in repository tests.
Q: How do you test @Scheduled jobs without waiting?
A: Extract the job's logic into a bean method, and test it directly. For the scheduling wiring, use a short fixed delay in tests, with Awaitility, or disable scheduling in tests and trigger the method manually.