skip to content

When should you choose @JdbcTest / @JsonTest over @SpringBootTest, and what are the design tradeoffs of test slices?

level: principalimportance: nice to knowfreq 24%

answer

  1. Slice = one layer, minimal context, fast
  2. @SpringBootTest = full wiring / real HTTP
  3. Fidelity vs speed tradeoff
  4. Context cache key = config + @Import + @MockBean
  5. Pyramid: many slices, few full integration

basics

~20 s

Use narrow slices like @JdbcTest and @JsonTest to test one layer fast and in isolation with a tiny context; use @SpringBootTest when you need the whole wired application (cross-layer integration, real beans). Slices trade fidelity and coverage for speed and focus.

solid answer

~40 s

Slices exist to test a single concern with the smallest possible application context: @JdbcTest for JdbcTemplate persistence, @JsonTest for serialization, @WebMvcTest for controllers, etc. Benefits: fast startup, strong isolation, cached contexts, and a clear failure signal scoped to one layer. Costs: they deliberately omit most beans, so cross-cutting wiring, real HTTP, security filters, and inter-module behavior are not exercised; and defaults like H2 substitution can diverge from production. @SpringBootTest loads the full context (optionally a real web server) and is the right tool for genuine end-to-end/integration coverage, at the price of speed and more context caches. A healthy test pyramid uses many slice/unit tests and a few @SpringBootTest integration tests. Watch context-cache proliferation: every distinct slice configuration/@Import/@MockBean combination is a new cached ApplicationContext, which can dominate suite time.

code

java · 21 lines
java
// Slice: fast, one layer, minimal context
@JsonTest
class PriceJsonTest {
    @Autowired JacksonTester<Price> json;
    @Test void formatsCurrency() throws Exception {
        assertThat(json.write(new Price("USD", 9.99)))
            .extractingJsonPathStringValue("$.currency").isEqualTo("USD");
    }
}

// Full context: cross-layer integration when the slice is not enough
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@Testcontainers
class CheckoutIntegrationTest {
    @Autowired TestRestTemplate rest;
    @Test void placesOrderEndToEnd() {
        ResponseEntity<OrderView> res =
            rest.postForEntity("/orders", new OrderRequest("A-1"), OrderView.class);
        assertThat(res.getStatusCode()).isEqualTo(HttpStatus.CREATED);
    }
}

go deeper

for a junior

Know slices are faster and narrower than @SpringBootTest.

for a middle

Match the slice to the layer and know when a full context is required.

for a senior

Reason about fidelity gaps, Testcontainers for real DBs, and the test pyramid.

for a principal

Manage context-cache economics across the suite, set org conventions on slice-vs-integration boundaries and fidelity requirements, and balance CI cost against coverage.

## The core idea Spring Boot **test slices** (`@JdbcTest`, `@JsonTest`, `@WebMvcTest`, `@DataJpaTest`, `@RestClientTest`, …) each activate only the auto-configurations for **one layer** and switch the rest off. This is implemented with `@…Test` meta-annotations plus `@TypeExcludeFilter`s that keep unrelated `@Component`s out of the context. The payoff is a **minimal ApplicationContext**: fewer beans to instantiate, faster startup, and a failure that points at exactly one layer. ## When to pick a slice - **`@JdbcTest`** — you have plain-JDBC repositories and want to verify SQL + `RowMapper` mapping cheaply, with rollback isolation. - **`@JsonTest`** — you want to pin the exact JSON wire contract of DTOs (field names, formatting, custom serializers), independent of controllers. - General rule: if the behavior lives **entirely within one layer** and you can supply its collaborators via `@Import`/`@MockBean`, a slice is faster and clearer. ## When to pick `@SpringBootTest` - You are validating **cross-layer** behavior: controller → service → repository → DB together. - You need the **real HTTP stack** (`webEnvironment = RANDOM_PORT` + `TestRestTemplate`/`WebTestClient`), the **security filter chain**, transaction propagation across beans, or module-to-module events. - You want an **integration smoke test** that the whole context actually wires and starts. ## Tradeoffs to articulate (principal-level) 1. **Speed vs fidelity.** Slices are fast but omit beans; the thing that breaks in prod (a filter, an interceptor, a real dialect) may be exactly what the slice excluded. Example: `@JdbcTest` on H2 can pass while Postgres-specific SQL fails — hence `Replace.NONE` + Testcontainers for dialect-sensitive tests. 2. **Isolation vs realism.** `@JsonTest` uses the real ObjectMapper (good fidelity for JSON) but no controller/validation/exception-handling around it; a `@WebMvcTest` or `@SpringBootTest` is needed to test the full request→response including error mapping. 3. **Context caching.** The TestContext framework **caches ApplicationContexts by configuration key**. Each unique combination of slice + `@Import` + `@MockBean` + properties creates a **new** cached context. Sprawling, slightly-different slices can blow up memory and total runtime *more* than a few shared full-context tests. Standardize base test configs and `@MockBean` sets to maximize cache reuse. 4. **Maintenance signal.** Slices give sharper failure localization, which speeds debugging and keeps the pyramid healthy (many fast, few slow). 5. **Coupling to Boot auto-config.** Slices depend on Boot's auto-configuration model; heavy manual `@Configuration` or non-Boot setups may not slice cleanly. ## Test-pyramid guidance - **Base:** unit tests (no Spring). - **Middle:** slices (`@JdbcTest`, `@JsonTest`, `@WebMvcTest`, `@DataJpaTest`) — the bulk of Spring-aware tests. - **Top:** a small number of `@SpringBootTest` integration tests, ideally with Testcontainers for real infrastructure. ## Concrete decision checklist - Behavior in one layer, collaborators mockable/importable → **slice**. - Need real HTTP/security/cross-bean transactions/events → **@SpringBootTest**. - Dialect-sensitive SQL → slice **with** `Replace.NONE` + Testcontainers, or a full integration test. - Many near-duplicate contexts appearing → consolidate config to reuse the context cache.

  • Your CI test suite is slow despite mostly using slices. What slice-specific cause should you investigate?
    Context-cache proliferation: each distinct slice + @Import + @MockBean + properties combination builds and caches a separate ApplicationContext. Consolidate base test configs and mock sets so contexts are reused instead of rebuilt per class.
  • Why can a passing @JdbcTest still miss a production bug that @SpringBootTest would catch?
    The slice omits most beans (security filters, interceptors, other layers) and defaults to H2. Bugs in cross-layer wiring, request handling, or Postgres-specific SQL live outside the slice's scope, so only a fuller context or a real-DB test exercises them.

saying these in an interview costs you the question

  • Claiming slices start the full application context (they intentionally do not).
  • Believing more @MockBean/@Import variety is free — it multiplies cached contexts.
  • Using @SpringBootTest for everything, ignoring slice speed/isolation.
  • Assuming H2-based slices give production-fidelity SQL coverage.

context