Files
awesome-copilot/skills/spring-boot-testing/references/mockmvc-classic.md
Kartik Dhiman e4fc57f204 feat: add spring-boot-testing skill for Spring Boot 4.0 (#1085)
- Introduced MockMvcTester for AssertJ-style assertions in Spring MVC testing.
- Added @RestClientTest for testing REST clients with MockRestServiceServer.
- Implemented RestTestClient as a modern alternative to TestRestTemplate.
- Documented migration steps from Spring Boot 3.x to 4.0, including dependency and annotation changes.
- Created an overview of test slices to guide testing strategies.
- Included Testcontainers setup for JDBC testing with PostgreSQL and MySQL.
- Enhanced @WebMvcTest documentation with examples for various HTTP methods and validation.
2026-03-20 10:24:37 +11:00

207 lines
5.8 KiB
Markdown

# MockMvc Classic
Traditional MockMvc API for Spring MVC controller tests (pre-Spring Boot 3.2 or legacy codebases).
## When to Use This Reference
- The project uses Spring Boot < 3.2 (no `MockMvcTester` available)
- Existing tests use `mvc.perform(...)` and you are maintaining or extending them
- You need to migrate classic MockMvc tests to `MockMvcTester` (see migration section below)
- The user explicitly asks about `ResultActions`, `andExpect()`, or Hamcrest-style web assertions
For new tests on Spring Boot 3.2+, prefer [mockmvc-tester.md](mockmvc-tester.md) instead.
## Setup
```java
@WebMvcTest(OrderController.class)
class OrderControllerTest {
@Autowired
private MockMvc mvc;
@MockBean
private OrderService orderService;
}
```
## Basic GET Request
```java
@Test
void shouldReturnOrder() throws Exception {
given(orderService.findById(1L)).willReturn(new Order(1L, "PENDING", 99.99));
mvc.perform(get("/orders/1"))
.andExpect(status().isOk())
.andExpect(content().contentType(MediaType.APPLICATION_JSON))
.andExpect(jsonPath("$.id").value(1))
.andExpect(jsonPath("$.status").value("PENDING"))
.andExpect(jsonPath("$.totalToPay").value(99.99));
}
```
## POST with Request Body
```java
@Test
void shouldCreateOrder() throws Exception {
given(orderService.create(any(OrderRequest.class))).willReturn(1L);
mvc.perform(post("/orders")
.contentType(MediaType.APPLICATION_JSON)
.content("{\"product\": \"Laptop\", \"quantity\": 2}"))
.andExpect(status().isCreated())
.andExpect(header().string("Location", "/orders/1"));
}
```
## PUT Request
```java
@Test
void shouldUpdateOrder() throws Exception {
mvc.perform(put("/orders/1")
.contentType(MediaType.APPLICATION_JSON)
.content("{\"status\": \"COMPLETED\"}"))
.andExpect(status().isOk());
}
```
## DELETE Request
```java
@Test
void shouldDeleteOrder() throws Exception {
mvc.perform(delete("/orders/1"))
.andExpect(status().isNoContent());
}
```
## Status Matchers
```java
.andExpect(status().isOk()) // 200
.andExpect(status().isCreated()) // 201
.andExpect(status().isNoContent()) // 204
.andExpect(status().isBadRequest()) // 400
.andExpect(status().isUnauthorized()) // 401
.andExpect(status().isForbidden()) // 403
.andExpect(status().isNotFound()) // 404
.andExpect(status().is(422)) // arbitrary code
```
## JSON Path Assertions
```java
// Exact value
.andExpect(jsonPath("$.status").value("PENDING"))
// Existence
.andExpect(jsonPath("$.id").exists())
.andExpect(jsonPath("$.deletedAt").doesNotExist())
// Array size
.andExpect(jsonPath("$.items").isArray())
.andExpect(jsonPath("$.items", hasSize(3)))
// Nested field
.andExpect(jsonPath("$.customer.name").value("John Doe"))
.andExpect(jsonPath("$.customer.address.city").value("Berlin"))
// With Hamcrest matchers
.andExpect(jsonPath("$.total", greaterThan(0.0)))
.andExpect(jsonPath("$.description", containsString("order")))
```
## Content Assertions
```java
.andExpect(content().contentType(MediaType.APPLICATION_JSON))
.andExpect(content().contentTypeCompatibleWith(MediaType.APPLICATION_JSON))
.andExpect(content().string(containsString("PENDING")))
.andExpect(content().json("{\"status\":\"PENDING\"}"))
```
## Header Assertions
```java
.andExpect(header().string("Location", "/orders/1"))
.andExpect(header().string("Content-Type", containsString("application/json")))
.andExpect(header().exists("X-Request-Id"))
.andExpect(header().doesNotExist("X-Deprecated"))
```
## Request Parameters and Headers
```java
// Query parameters
mvc.perform(get("/orders").param("status", "PENDING").param("page", "0"))
.andExpect(status().isOk());
// Path variables
mvc.perform(get("/orders/{id}", 1L))
.andExpect(status().isOk());
// Request headers
mvc.perform(get("/orders/1").header("X-Api-Key", "secret"))
.andExpect(status().isOk());
```
## Capturing the Response
```java
@Test
void shouldReturnCreatedId() throws Exception {
given(orderService.create(any())).willReturn(42L);
MvcResult result = mvc.perform(post("/orders")
.contentType(MediaType.APPLICATION_JSON)
.content("{\"product\": \"Laptop\", \"quantity\": 1}"))
.andExpect(status().isCreated())
.andReturn();
String location = result.getResponse().getHeader("Location");
assertThat(location).isEqualTo("/orders/42");
}
```
## Chaining with andDo
```java
mvc.perform(get("/orders/1"))
.andDo(print()) // prints request/response to console (debug)
.andExpect(status().isOk());
```
## Static Imports
```java
import org.springframework.boot.test.mock.mockito.MockBean;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.*;
import static org.hamcrest.Matchers.*;
```
## Migration to MockMvcTester
| Classic MockMvc | MockMvcTester (recommended) |
| --- | --- |
| `@Autowired MockMvc mvc` | `@Autowired MockMvcTester mvc` |
| `mvc.perform(get("/orders/1"))` | `mvc.get().uri("/orders/1")` |
| `.andExpect(status().isOk())` | `.hasStatusOk()` |
| `.andExpect(jsonPath("$.status").value("X"))` | `.bodyJson().convertTo(T.class)` + AssertJ |
| `throws Exception` on every method | No checked exception |
| Hamcrest matchers | AssertJ fluent assertions |
See [mockmvc-tester.md](mockmvc-tester.md) for the full modern API.
## Key Points
1. **Every test method must declare `throws Exception`**`perform()` throws checked exceptions
2. **Use `andDo(print())` during debugging** — remove before committing
3. **Prefer `jsonPath()` over `content().string()`** — more precise field-level assertions
4. **Static imports are required** — IDE can auto-add them
5. **Migrate to MockMvcTester** when upgrading to Spring Boot 3.2+ for better readability