When it comes to Spring MVC and our REST controllers, it's important to make sure the app handles requests correctly. Imagine your controller is supposed to do a complex job like accepting a pizza order, but instead it sends you khachapuri. MockMvc lets you test scenarios like that (well, not literally about pizza, but about correct request handling).
MockMvc helps you:
- Verify routes (for example,
GET /api/orders/{id}). - Make sure endpoints return correct HTTP statuses (200, 404).
- Check that the response body contains exactly what you expect (JSON, XML, etc.).
- Catch bugs before a QA tester finds them and pokes at your sensitive spots.
MockMvc is part of spring-test — a library that's included in the Spring Framework. So you don't need to install anything extra if you're already using Spring in your project.
Configuring MockMvc
First, let's see how to set up the environment. You can use the @AutoConfigureMockMvc annotation, which will automatically configure MockMvc for your application.
Example setup:
@SpringBootTest
@AutoConfigureMockMvc
public class OrderControllerTest {
@Autowired
private MockMvc mockMvc; // MockMvc will be configured automatically
// Tests will go here
}
If you're feeling heroic and want to set everything up manually, here's how to do it:
@BeforeEach
void setUp() {
mockMvc = MockMvcBuilders.standaloneSetup(new OrderController()).build();
}
Writing a basic test with MockMvc
Let's try testing the simplest controller. Say we have a controller that returns a greeting message.
@RestController
@RequestMapping("/api/hello")
public class HelloController {
@GetMapping
public ResponseEntity<String> sayHello() {
return ResponseEntity.ok("Hello, world!");
}
}
Now let's test it:
@SpringBootTest
@AutoConfigureMockMvc
public class HelloControllerTest {
@Autowired
private MockMvc mockMvc;
@Test
public void testSayHello() throws Exception {
mockMvc.perform(get("/api/hello")) // Simulate a GET request
.andExpect(status().isOk()) // Check that the response status is 200
.andExpect(content().string("Hello, world!")); // Check that the response body contains "Hello, world!"
}
}
Let's break down what's happening here:
mockMvc.perform(get("/api/hello"))— we simulate an HTTP GET request.andExpect(status().isOk())— we check that HTTP status 200 is returned.andExpect(content().string("Hello, world!"))— we ensure the response body contains the expected string.
Checking JSON responses
Most of your controllers will probably return JSON. MockMvc makes working with JSON really convenient.
Example controller:
@RestController
@RequestMapping("/api/orders")
public class OrderController {
@GetMapping("/{id}")
public ResponseEntity<OrderDTO> getOrder(@PathVariable Long id) {
OrderDTO order = new OrderDTO(id, "Pizza", 2); // Return a "stub" object
return ResponseEntity.ok(order);
}
}
Example DTO class:
public class OrderDTO {
private Long id;
private String name;
private int quantity;
// Constructors, getters, and setters
}
Now let's write the test:
@SpringBootTest
@AutoConfigureMockMvc
public class OrderControllerTest {
@Autowired
private MockMvc mockMvc;
@Test
public void testGetOrder() throws Exception {
mockMvc.perform(get("/api/orders/1")) // Simulate a GET request
.andExpect(status().isOk()) // Check 200 OK status
.andExpect(jsonPath("$.id").value(1)) // Check the "id" field
.andExpect(jsonPath("$.name").value("Pizza")) // Check the "name" field
.andExpect(jsonPath("$.quantity").value(2)); // Check the "quantity" field
}
}
The key thing here is jsonPath. This method helps extract values from JSON objects and assert them. For example:
$.id— this is the path to theidfield in the root object.
Checking HTTP headers
If your controller adds custom headers to the response, MockMvc has you covered there too.
Example controller:
@RestController
@RequestMapping("/api/headers")
public class HeaderController {
@GetMapping
public ResponseEntity<String> getHeader() {
return ResponseEntity.ok()
.header("X-Custom-Header", "HeaderValue")
.body("Response with a header");
}
}
Test:
@SpringBootTest
@AutoConfigureMockMvc
public class HeaderControllerTest {
@Autowired
private MockMvc mockMvc;
@Test
public void testCustomHeader() throws Exception {
mockMvc.perform(get("/api/headers"))
.andExpect(status().isOk()) // Check 200 status
.andExpect(header().string("X-Custom-Header", "HeaderValue")); // Check the header
}
}
Handling POST requests
Sometimes you need to test endpoints that accept data from the client. Imagine we have a controller that handles creating new orders.
Example controller:
@RestController
@RequestMapping("/api/orders")
public class OrderController {
@PostMapping
public ResponseEntity<OrderDTO> createOrder(@RequestBody OrderDTO order) {
order.setId(100L); // Set the created order ID
return ResponseEntity.status(HttpStatus.CREATED).body(order);
}
}
Test:
@SpringBootTest
@AutoConfigureMockMvc
public class OrderControllerTest {
@Autowired
private MockMvc mockMvc;
@Test
public void testCreateOrder() throws Exception {
String orderJson = "{ \"name\": \"Pizza\", \"quantity\": 1 }";
mockMvc.perform(post("/api/orders")
.contentType(MediaType.APPLICATION_JSON) // Specify content type
.content(orderJson)) // Send the request body
.andExpect(status().isCreated()) // Check 201 Created status
.andExpect(jsonPath("$.id").value(100)) // Check that ID is set
.andExpect(jsonPath("$.name").value("Pizza")) // Check the name
.andExpect(jsonPath("$.quantity").value(1)); // Check the quantity
}
}
Tips and common mistakes
When using MockMvc, make sure that:
- You specify routes correctly. People often forget to set the base
@RequestMappingon the controller. - Headers and content types match what your endpoint expects. For example, if you're sending JSON, don't forget
contentType(MediaType.APPLICATION_JSON).
If MockMvc throws an error saying it can't find the controller, check whether @SpringBootTest is configured and whether your controller is in the application context.
MockMvc is your first step toward well-tested controllers. In the next lectures we'll dive into writing more advanced tests and explore integration with real databases.
GO TO FULL VERSION