skip to content

When would you choose @WebMvcTest over @SpringBootTest, and what are the tradeoffs?

level: seniorimportance: should knowfreq 68%

answer

  1. slice = fast + web only + mocks
  2. @SpringBootTest = full context, slow
  3. webEnvironment MOCK/RANDOM_PORT/NONE
  4. mock drift = false confidence
  5. context caching per unique config

basics

~10 s

Use @WebMvcTest for fast, isolated web-layer tests (routing, validation, JSON, error handling) with mocked services. Use @SpringBootTest when you need the full context — real service/repository wiring or true end-to-end integration.

solid answer

~40 s

@WebMvcTest is a slice: it loads only MVC beans and an offline MockMvc, mocking collaborators, so tests start fast and pin down web concerns — path mapping, @Valid handling, serialization, status/headers, and @ControllerAdvice error mapping. Its tradeoff is that nothing below the controller is real, so it can't catch integration bugs in services, transactions, or persistence, and mocks can drift from real behavior. @SpringBootTest boots the whole context (optionally a real servlet on webEnvironment=RANDOM_PORT with TestRestTemplate/WebTestClient), exercising real wiring end to end, but is far slower and more brittle. A good suite uses @WebMvcTest for controller-focused cases, plain unit tests for service logic, @DataJpaTest for repositories, and a few @SpringBootTest cases for critical end-to-end paths — the classic test pyramid.

code

java · 14 lines
java
// Slice: web layer only, service mocked
@WebMvcTest(PaymentController.class)
class PaymentControllerSliceTest {
    @Autowired MockMvc mockMvc;
    @MockitoBean PaymentService paymentService;
    // ...validation, JSON, status code assertions...
}

// Full integration: real wiring over a real port
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class PaymentE2ETest {
    @Autowired TestRestTemplate rest; // real HTTP call
    // ...controller -> service -> repository -> DB...
}

go deeper

for a junior

Knows @WebMvcTest is faster and narrower than @SpringBootTest.

for a middle

Can list which concerns belong to each and knows webEnvironment options.

for a senior

Articulates tradeoffs (mock drift, isolation vs coverage) and maps tests to the pyramid.

for a principal

Reasons about context caching, suite performance, and an org-wide testing strategy across slices.

## The two tools **`@WebMvcTest`** — a *slice* test. Loads only the Spring MVC layer (controllers, `@ControllerAdvice`, filters, converters) plus an auto-configured **`MockMvc`** that dispatches requests through the `DispatcherServlet` **without** a running server or socket. Collaborators are absent and must be mocked (`@MockitoBean`). **`@SpringBootTest`** — loads the **entire** `ApplicationContext` (all `@Component`/`@Service`/`@Repository`, configuration, persistence). Its `webEnvironment` attribute controls the web tier: - `MOCK` (default): mock servlet environment; you still use `MockMvc` (via `@AutoConfigureMockMvc`) but against the *full* context. - `RANDOM_PORT` / `DEFINED_PORT`: starts a **real** embedded server; you call it over HTTP with `TestRestTemplate` or `WebTestClient`. - `NONE`: no web environment. ## Decision criteria Choose **`@WebMvcTest`** when the thing under test is a **web-layer concern**: - URL/HTTP-method routing and path variables/params binding. - Bean validation (`@Valid`) and the resulting `400` + error body. - JSON (de)serialization edge cases (Jackson config, custom serializers). - Exception → response mapping via `@ControllerAdvice`. - Security rules on endpoints (with security test support). Benefit: **speed and isolation** — a slice starts in a fraction of the time and fails for one clear reason. Choose **`@SpringBootTest`** when you need **real collaboration**: - Verifying the controller→service→repository→DB path actually works together. - Transaction boundaries, real Jackson + real service serialization together. - Smoke/end-to-end tests over a real port. Cost: **slow startup**, heavier resource use, more moving parts that can flake, and shared context caching complexity. ## Tradeoffs / risks of the slice - **False confidence from mocks**: a `@WebMvcTest` passing says nothing about whether the real service behaves as stubbed. Mock/real drift is real. - **Missing beans**: any bean the controller (transitively, at construction) needs must be provided; over-narrow slices need extra mocks, over-broad (`@WebMvcTest` with no arg) load all controllers. - **Security surprises**: with Spring Security on the classpath, the filter chain applies; endpoints may 401/403 unless you add `@WithMockUser` or a test security config. ## Context caching note Spring's `TestContext` framework **caches** contexts by configuration. Many different slice configurations (different `controllers=`, different `@MockitoBean` sets) each create a **new** cached context; a sprawl of unique slice configs can actually *slow* a suite. Standardizing slice setups improves cache reuse. ## Rule of thumb Controller behavior → `@WebMvcTest`. Service logic → plain JUnit + Mockito (no Spring). Repository/queries → `@DataJpaTest`. A handful of critical journeys → `@SpringBootTest(RANDOM_PORT)`. This is the test pyramid: many fast focused tests, few slow broad ones.

  • Does @SpringBootTest use MockMvc or a real HTTP client?
    It depends on webEnvironment. With MOCK (default) you use MockMvc against the full context via @AutoConfigureMockMvc; with RANDOM_PORT/DEFINED_PORT a real server starts and you use TestRestTemplate or WebTestClient over real HTTP.
  • How can having many @WebMvcTest classes slow a build despite each being fast?
    Each distinct slice configuration creates a separate cached ApplicationContext; many unique configs defeat context caching, so total startup cost across the suite rises. Standardizing slices improves reuse.

saying these in an interview costs you the question

  • Claiming @WebMvcTest tests the full request-to-DB path
  • Saying @SpringBootTest is always better because it's 'real'
  • Believing @WebMvcTest starts an embedded server on a port
  • Ignoring that mocks can diverge from real service behavior

context