java
59 lines · 8 steps
Slice testing a controller with @WebMvcTest in Spring
A focused web-layer test that loads only the controller, mocks the service, and asserts HTTP behavior end to end.
Explained by
highlit
1@WebMvcTest(OrderController.class)
2class OrderControllerTest {
3
4 @Autowired
5 private MockMvc mockMvc;
6
7 @Autowired
8 private ObjectMapper objectMapper;
9
10 @MockBean
11 private OrderService orderService;
12
13 @Test
14 void returnsOrderWhenFound() throws Exception {
15 var order = new OrderResponse(42L, "SHIPPED", new BigDecimal("129.99"));
16 when(orderService.findById(42L)).thenReturn(order);
17
18 mockMvc.perform(get("/api/orders/{id}", 42L))
19 .andExpect(status().isOk())
20 .andExpect(jsonPath("$.id").value(42))
21 .andExpect(jsonPath("$.status").value("SHIPPED"))
22 .andExpect(jsonPath("$.total").value(129.99));
23 }
24
25 @Test
26 void returns404WhenMissing() throws Exception {
27 when(orderService.findById(99L)).thenThrow(new OrderNotFoundException(99L));
28
29 mockMvc.perform(get("/api/orders/{id}", 99L))
30 .andExpect(status().isNotFound())
31 .andExpect(jsonPath("$.message").value("Order 99 not found"));
32 }
33
34 @Test
35 void createsOrderFromValidPayload() throws Exception {
36 var request = new CreateOrderRequest("ACME-7", 3);
37 when(orderService.create(any(CreateOrderRequest.class)))
38 .thenReturn(new OrderResponse(7L, "PENDING", new BigDecimal("59.97")));
39
40 mockMvc.perform(post("/api/orders")
41 .contentType(MediaType.APPLICATION_JSON)
42 .content(objectMapper.writeValueAsString(request)))
43 .andExpect(status().isCreated())
44 .andExpect(header().string("Location", "/api/orders/7"))
45 .andExpect(jsonPath("$.status").value("PENDING"));
46 }
47
48 @Test
49 void rejectsInvalidPayload() throws Exception {
50 var request = new CreateOrderRequest("", 0);
51
52 mockMvc.perform(post("/api/orders")
53 .contentType(MediaType.APPLICATION_JSON)
54 .content(objectMapper.writeValueAsString(request)))
55 .andExpect(status().isBadRequest());
56
57 verifyNoInteractions(orderService);
58 }
59}
01 / 01
STEP 01
‹ swipe to step through ›
Walkthrough
Space play
←→ step
click any line
Three takeaways
- 1@WebMvcTest loads just the web layer so tests stay fast and focused on request handling.
- 2Mocking the service with @MockBean lets each test pin down exactly what the controller receives.
- 3MockMvc lets you assert status codes, headers, and JSON fields without a running server.
Related explainers
java
public class ThumbnailProcessor { private static final int MAX_CONCURRENCY = 4;
Bounded parallel thumbnail rendering in Java
concurrency
thread-pool
futures
Intermediate
7 steps
java
public class SortedListMerger { public static int[] merge(int[] a, int[] b) { int[] result = new int[a.length + b.length];
Merging two sorted arrays in Java
two-pointers
merging
arrays
Beginner
6 steps
java
import java.util.ArrayDeque; import java.util.Deque; public final class RollingAverage {
A rolling average over a sliding window
sliding-window
running-sum
deque
Intermediate
7 steps
java
@Target({ElementType.FIELD, ElementType.PARAMETER}) @Retention(RetentionPolicy.RUNTIME) @Constraint(validatedBy = StrongPasswordValidator.class) @Documented
Building a custom @StrongPassword validator in Spring
bean-validation
annotations
regex
Intermediate
7 steps
java
@Component public class JwtAuthenticationFilter extends OncePerRequestFilter { private final JwtTokenProvider tokenProvider;
How a JWT auth filter works in Spring
authentication
jwt
servlet-filter
Intermediate
8 steps
java
@Entity @Table(name = "accounts") public class Account {
Optimistic locking with @Version in Spring
optimistic-locking
jpa
concurrency
Advanced
7 steps
Share this explainer
Here's the card — post it anywhere.
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code
Embed this explainer
Drop the interactive walkthrough into a blog or docs. Views never cost a credit.
<iframe src="https://highlit.co/explainers/slice-testing-a-controller-with-webmvctest-in-spring-explained-java-bcf0/embed?autoplay=1" width="100%" height="520" loading="lazy" style="border:0"></iframe>
Autoplay is on by default — add ?autoplay=0 to start paused.