mirror of
https://github.com/github/awesome-copilot.git
synced 2026-03-21 00:25:13 +00:00
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.
This commit is contained in:
177
skills/spring-boot-testing/references/webmvctest.md
Normal file
177
skills/spring-boot-testing/references/webmvctest.md
Normal file
@@ -0,0 +1,177 @@
|
||||
# @WebMvcTest
|
||||
|
||||
Testing Spring MVC controllers with focused slice tests.
|
||||
|
||||
## Basic Structure
|
||||
|
||||
```java
|
||||
@WebMvcTest(OrderController.class)
|
||||
class OrderControllerTest {
|
||||
|
||||
@Autowired
|
||||
private MockMvcTester mvc;
|
||||
|
||||
@MockitoBean
|
||||
private OrderService orderService;
|
||||
|
||||
@MockitoBean
|
||||
private UserService userService;
|
||||
}
|
||||
```
|
||||
|
||||
## What Gets Loaded
|
||||
|
||||
- The specified controller(s)
|
||||
- Spring MVC infrastructure (HandlerMapping, HandlerAdapter)
|
||||
- Jackson ObjectMapper (for JSON)
|
||||
- Exception handlers (@ControllerAdvice)
|
||||
- Spring Security filters (if on classpath)
|
||||
- Validation (if on classpath)
|
||||
|
||||
## Testing GET Endpoints
|
||||
|
||||
```java
|
||||
@Test
|
||||
void shouldReturnOrder() {
|
||||
var order = new Order(1L, "PENDING", BigDecimal.valueOf(99.99));
|
||||
given(orderService.findById(1L)).willReturn(order);
|
||||
|
||||
assertThat(mvc.get().uri("/orders/1"))
|
||||
.hasStatusOk()
|
||||
.hasContentType(MediaType.APPLICATION_JSON)
|
||||
.bodyJson()
|
||||
.extractingPath("$.status")
|
||||
.isEqualTo("PENDING");
|
||||
}
|
||||
```
|
||||
|
||||
## Testing POST with Request Body
|
||||
|
||||
### Using Text Blocks (Java 25)
|
||||
|
||||
```java
|
||||
@Test
|
||||
void shouldCreateOrder() {
|
||||
given(orderService.create(any(OrderRequest.class))).willReturn(1L);
|
||||
|
||||
var json = """
|
||||
{
|
||||
"product": "Product A",
|
||||
"quantity": 2
|
||||
}
|
||||
""";
|
||||
|
||||
assertThat(mvc.post().uri("/orders")
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content(json))
|
||||
.hasStatus(HttpStatus.CREATED)
|
||||
.hasHeader("Location", "/orders/1");
|
||||
}
|
||||
```
|
||||
|
||||
### Using Records
|
||||
|
||||
```java
|
||||
record OrderRequest(String product, int quantity) {}
|
||||
|
||||
@Test
|
||||
void shouldCreateOrderWithRecord() {
|
||||
var request = new OrderRequest("Product A", 2);
|
||||
given(orderService.create(any())).willReturn(1L);
|
||||
|
||||
assertThat(mvc.post().uri("/orders")
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content(json.write(request).getJson()))
|
||||
.hasStatus(HttpStatus.CREATED);
|
||||
}
|
||||
```
|
||||
|
||||
## Testing Validation Errors
|
||||
|
||||
```java
|
||||
@Test
|
||||
void shouldRejectInvalidOrder() {
|
||||
var invalidJson = """
|
||||
{
|
||||
"product": "",
|
||||
"quantity": -1
|
||||
}
|
||||
""";
|
||||
|
||||
assertThat(mvc.post().uri("/orders")
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content(invalidJson))
|
||||
.hasStatus(HttpStatus.BAD_REQUEST)
|
||||
.bodyJson()
|
||||
.hasPath("$.errors");
|
||||
}
|
||||
```
|
||||
|
||||
## Testing Query Parameters
|
||||
|
||||
```java
|
||||
@Test
|
||||
void shouldFilterOrdersByStatus() {
|
||||
assertThat(mvc.get().uri("/orders?status=PENDING"))
|
||||
.hasStatusOk();
|
||||
|
||||
verify(orderService).findByStatus(OrderStatus.PENDING);
|
||||
}
|
||||
```
|
||||
|
||||
## Testing Path Variables
|
||||
|
||||
```java
|
||||
@Test
|
||||
void shouldCancelOrder() {
|
||||
assertThat(mvc.put().uri("/orders/123/cancel"))
|
||||
.hasStatusOk();
|
||||
|
||||
verify(orderService).cancel(123L);
|
||||
}
|
||||
```
|
||||
|
||||
## Testing with Security
|
||||
|
||||
```java
|
||||
@Test
|
||||
@WithMockUser(roles = "ADMIN")
|
||||
void adminShouldDeleteOrder() {
|
||||
assertThat(mvc.delete().uri("/orders/1"))
|
||||
.hasStatus(HttpStatus.NO_CONTENT);
|
||||
}
|
||||
|
||||
@Test
|
||||
void anonymousUserShouldBeForbidden() {
|
||||
assertThat(mvc.delete().uri("/orders/1"))
|
||||
.hasStatus(HttpStatus.UNAUTHORIZED);
|
||||
}
|
||||
```
|
||||
|
||||
## Multiple Controllers
|
||||
|
||||
```java
|
||||
@WebMvcTest({OrderController.class, ProductController.class})
|
||||
class WebLayerTest {
|
||||
// Tests multiple controllers in one slice
|
||||
}
|
||||
```
|
||||
|
||||
## Excluding Auto-Configuration
|
||||
|
||||
```java
|
||||
@WebMvcTest(OrderController.class)
|
||||
@AutoConfigureMockMvc(addFilters = false) // Skip security filters
|
||||
class OrderControllerWithoutSecurityTest {
|
||||
// Tests without security filters
|
||||
}
|
||||
```
|
||||
|
||||
## Key Points
|
||||
|
||||
1. Always mock services with @MockitoBean
|
||||
2. Use MockMvcTester for AssertJ-style assertions
|
||||
3. Test HTTP semantics (status, headers, content-type)
|
||||
4. Verify service method calls when side effects matter
|
||||
5. Don't test business logic here - that's for unit tests
|
||||
6. Leverage Java 25 text blocks for JSON payloads
|
||||
Reference in New Issue
Block a user