@WebMvcTest — testando controllers com MockMvc
TL;DR
@WebMvcTestcarrega só a camada web (controllers,@ControllerAdvice, converters, filters) e mocka o resto da aplicação. OMockMvcdispara requests sem subir servidor — direto noDispatcherServlet, em memória — e assere status HTTP e corpo JSON viajsonPath. É o slice certo pra testar controllers isoladamente: rápido, focado, sem banco nem rede.
O que é
@WebMvcTest é um slice de teste do Spring Boot que auto-configura apenas a infraestrutura do Spring MVC. Em vez de subir o ApplicationContext inteiro (como faria @SpringBootTest), ele monta um contexto mínimo contendo só os beans da camada web — o suficiente pra que um controller responda a um request.
Junto com ele vem o MockMvc, já @Autowired-ável: um cliente que simula requisições HTTP sem container servlet real. O request entra pelo DispatcherServlet em memória, percorre o pipeline MVC normal (handler mapping, validação, serialização) e devolve a resposta — tudo sem abrir porta TCP.
A forma típica é declarar o controller sob teste explicitamente:
@WebMvcTest(OrderController.class)Isso restringe o slice a esse controller (mais os componentes web globais), tornando o teste rápido e previsível.
O pipeline MVC em si é território do Galho 9
O como o
DispatcherServletresolve handlers, despacha exceções e serializa o corpo está descrito em O pipeline do DispatcherServlet. Aqui o foco é testar esse pipeline, não reexplicá-lo.
Por que importa
Testar controller subindo a aplicação inteira é caro e frágil: você acopla o teste ao banco, à segurança, a serviços externos. Um bug num repository derruba o teste do controller, e você perde a localização da falha.
@WebMvcTest corta isso. Ele isola a responsabilidade do controller: receber um request, validar a entrada, delegar pra um service (mockado) e traduzir o resultado em status HTTP + corpo. É exatamente o contrato que o controller deve cumprir — nem mais, nem menos.
O ganho prático é triplo:
- Velocidade — contexto mínimo, sem JPA, sem datasource, sem auto-config pesada. Testes de controller rodam em milissegundos.
- Foco — quando o teste falha, o problema está no controller (ou no advice/converter), não em camadas abaixo.
- Cobertura do contrato HTTP — você assere os códigos de status (200, 404, 422) e o shape do JSON, que é o que o cliente da API enxerga.
Como funciona
@WebMvcTest carrega só a web layer (e o que NÃO carrega)
O slice escaneia e instancia apenas os componentes da camada web:
@Controller/@RestController@ControllerAdvice(o tratamento de exceções global)ConvertereGenericConverterFiltereHandlerInterceptorWebMvcConfigurereHandlerMethodArgumentResolver- componentes Jackson de (de)serialização
E não carrega o resto da aplicação:
@Service@Repository@Componentcomuns@ConfigurationProperties(a menos que incluído explicitamente)
Ou seja: o controller existe, mas as dependências dele (services) não estão no contexto. Se você não mockar, o contexto falha em subir por bean faltante. Esse é o ponto-chave do slice — ele força você a isolar.
MockMvc: perform + andExpect (status / jsonPath)
O MockMvc processa o request em memória e expõe uma API fluente de asserção:
mockMvc.perform(get("/orders/1"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.id").value(1))
.andExpect(jsonPath("$.status").value("CONFIRMED"));perform(...)dispara o request (get,post,put,deletevêm deMockMvcRequestBuilders).andExpect(status().isOk())assere o código HTTP (isNotFound()→ 404,isUnprocessableEntity()→ 422,isCreated()→ 201).andExpect(jsonPath("$.campo").value(...))navega o JSON da resposta e compara valores.$é a raiz;$.items[0].iddesce em listas.
Como não há servidor real, não há latência de rede nem porta aberta — o request atravessa o DispatcherServlet direto.
Mockar o service com @MockitoBean
A dependência do controller entra no contexto como mock do Mockito, via @MockitoBean (a anotação do Spring Framework que substituiu @MockBean):
@MockitoBean
OrderService orderService;O Spring registra esse mock no contexto de teste no lugar do bean real. Você programa o comportamento com a API normal do Mockito:
given(orderService.findById(1L))
.willReturn(new Order(1L, "CONFIRMED"));Assim o controller recebe respostas determinísticas do service, e o teste fica responsável só pelo que o controller faz com elas.
Testar 200 / 404 / 422 / validação
Os quatro cenários que um teste de controller costuma cobrir:
- 200 OK — caminho feliz: service devolve a entidade, controller serializa, status 200.
- 404 Not Found — service lança
OrderNotFoundException; o@ControllerAdvice(que o slice carrega) traduz pra 404. - 422 Unprocessable Entity — corpo da requisição viola
@Valid; o advice traduz aMethodArgumentNotValidExceptionem 422 com a lista de erros. - Validação — assere que um body inválido não chega ao service (verificável com
verifyNoInteractions(orderService)).
Note que 404 e 422 só funcionam porque o @ControllerAdvice está no slice. É por isso que @WebMvcTest o carrega: o tratamento de exceção faz parte do contrato HTTP do controller.
Na prática
import static org.mockito.BDDMockito.given;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@WebMvcTest(OrderController.class)
class OrderControllerTest {
@Autowired
MockMvc mockMvc;
@MockitoBean
OrderService orderService;
@Test
void getReturns200AndBody() throws Exception {
given(orderService.findById(1L))
.willReturn(new Order(1L, "CONFIRMED"));
mockMvc.perform(get("/orders/1"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.id").value(1))
.andExpect(jsonPath("$.status").value("CONFIRMED"));
}
@Test
void getUnknownReturns404() throws Exception {
given(orderService.findById(99L))
.willThrow(new OrderNotFoundException(99L));
mockMvc.perform(get("/orders/99"))
.andExpect(status().isNotFound());
}
@Test
void postInvalidBodyReturns422() throws Exception {
String invalidBody = """
{ "customer": "" }
""";
mockMvc.perform(post("/orders")
.contentType(MediaType.APPLICATION_JSON)
.content(invalidBody))
.andExpect(status().isUnprocessableEntity());
verifyNoInteractions(orderService);
}
}O teste descreve só o contrato HTTP: dado o que o service retorna (ou lança), qual status e qual JSON o controller produz. Nada de banco, nada de servidor.
De onde vêm
OrderControllereOrderServiceO
@RestControllere seus mapeamentos (@GetMapping,@PostMapping,@PathVariable,@RequestBody @Valid) são do Galho 9 — veja @RestController e os mapeamentos. O@WebMvcTestapenas exercita esses mapeamentos.
Armadilhas
(1) Esperar que o service/repository reais carreguem
O erro mais comum: declarar @WebMvcTest(OrderController.class) e supor que o OrderService real (com seu OrderRepository, datasource etc.) vai estar disponível. Ele não vai — o slice não carrega @Service nem @Repository.
@WebMvcTest(OrderController.class)
class OrderControllerTest {
@Autowired MockMvc mockMvc;
// sem mock do OrderService → contexto falha:
// "No qualifying bean of type OrderService"
}Fix: declare a dependência como mock e programe-a:
@MockitoBean
OrderService orderService;
// given(orderService.findById(1L)).willReturn(...);(2) Testar serialização JSON complexa aqui em vez de @JsonTest
Tentar validar todas as regras de (de)serialização — formatos de data, campos ignorados, polimorfismo Jackson — dentro do @WebMvcTest. Isso mistura responsabilidades: o foco do slice web é o controller (roteamento, status, validação), não o motor de serialização.
// Anti-padrão: dezenas de jsonPath assertando cada nuance do mapeamento JSON
.andExpect(jsonPath("$.createdAt").value("2026-06-11T00:00:00Z"))
.andExpect(jsonPath("$.internalNote").doesNotExist())
// ... 20 linhas validando o serializer, não o controllerFix: valide o shape essencial no @WebMvcTest (1-2 campos-chave) e mova os testes finos do serializer pra um slice dedicado de JSON (@JsonTest), que carrega só o ObjectMapper configurado.
(3) Confundir MockMvc com teste de integração com servidor (porta)
MockMvc não sobe servidor — não há porta, não há cliente HTTP real, não há RestTemplate/WebTestClient batendo na rede. Tratar um teste de @WebMvcTest como se fosse end-to-end leva a expectativas erradas (ex.: achar que filtros de container, TLS ou o connector estão no caminho).
// Errado: isto NÃO é o que @WebMvcTest faz —
// não existe servidor real escutando numa porta aqui.Fix: quando o objetivo for testar a stack real com servidor de verdade, use @SpringBootTest(webEnvironment = RANDOM_PORT) com WebTestClient/TestRestTemplate — veja Testes de integração ponta a ponta. @WebMvcTest + MockMvc é deliberadamente in-memory.
Em entrevista
Frase pronta (inglês)
@WebMvcTestis a Spring Boot test slice that loads only the web layer — controllers,@ControllerAdvice, converters and filters — while leaving services and repositories out of the context. I autowire aMockMvcinstance to drive requests straight through theDispatcherServletin memory, with no servlet container or open port, and mock the controller’s service dependency with@MockitoBean. From there I assert the HTTP contract:status().isOk()for the happy path,isNotFound()andisUnprocessableEntity()for error cases handled by the advice, andjsonPathmatchers for the response body. It keeps controller tests fast and focused, so when one fails I know the bug is in the web layer rather than somewhere underneath.
Vocabulário
| Termo (EN) | Significado |
|---|---|
| test slice | fatia de contexto que carrega só parte da aplicação |
| web layer | camada web: controllers, advice, converters, filters |
| in-memory request | request processado sem container/porta, direto no dispatcher |
| mock bean | dependência substituída por mock no contexto (@MockitoBean) |
| status matcher | asserção sobre o código HTTP (status().isOk()) |
| JSON path assertion | asserção que navega o corpo JSON (jsonPath(...)) |
| controller advice | tratamento global de exceções (@ControllerAdvice) |
| HTTP contract | o acordo de status/corpo que a API expõe ao cliente |
Veja também
- Spring Boot Test e os slices
- Testes de integração ponta a ponta
- Testando a segurança
- @RestController e os mapeamentos
- O pipeline do DispatcherServlet
- Tratamento de exceções com @ControllerAdvice
- Testes (MOC do galho)
- Trilha Java
- Dicionário de Java
Referências
- Spring Boot Reference — Testing: Spring Boot Applications (
@WebMvcTest): https://docs.spring.io/spring-boot/reference/testing/spring-boot-applications.html - Spring Framework Reference — MockMvc: https://docs.spring.io/spring-framework/reference/testing/mockmvc/