Unit Testing Spring REST Controllers with MockMvc
This article opens a Rest Controller testing cycle and presents an approach to @RestController Unit Testing with MockMvc and Mockito.
In order to make the test quick, we will avoid building the whole spring context and allow MockMvc standalonesetup do its job. If you ever wondered how to use Mockito in your Rest Controller tests, you’ll find your answers here.
The code examples in this article are taken from allAroundJava’s DoctorBooking project and are available on Github.
Unit Testing Spring Rest Controllers
Before we move to unit testing itself, there are few rules that I use to make sure controllers are easily testable. To explain it visually, I’ll use an example of DoctorController, which code you can find at the bottom of this section and in the DoctorBooking github project.
Autowire Controller Dependencies Through Constructor
This is always a good idea as it allows to declare class fields as final. In case of tests it has additional benefits.
First, we can declare class dependencies freely within the test class. We can actually define different dependencies in each test method if we wish.
Second, wiring dependencies through constructor, eliminates the need to use any spring testing framework, making the test quicker to run.
Avoid complex logic in controllers
As we need to build mock server context, assert HTTP status codes and response content, testing Rest Controller is a little more cumbersome than testing a regular service class. It requires a little bit more code to set it up and it’s more difficult to assert on result as we’re receiving String content response. That’s why its much more convenient to elliminate logic inside the controller and move it to a service class. Testing service class is much easier since we can assert on strong typed objects. Once we move all logic to service methods, our Rest Controller will require much less test cases to confirm it’s working properly.
Avoid building Spring Context
Spring context is expensive to create and you can definitely notice that it takes a substantial amount of time to start it up. In order for our unit test to be quick, we will avoid building full Spring Context and use a MockMvc standalone setup, which will call our controller simulating a Web Server without actually starting it.
Here’s an example of DoctorController aplying some of the above principles.
@RestController
@RequestMapping("/doctors")
public class DoctorController {
private final DoctorService doctorService;
@Autowired
public DoctorController(DoctorService doctorService) {
this.doctorService = doctorService;
}
@RequestMapping(method = RequestMethod.GET,
produces = "application/xml", value = "/{id}")
public ResponseEntity<DoctorDto> getDoctor(@PathVariable("id") Long id) {
Optional<Doctor> doctorOptional = doctorService.getById(id);
Doctor doctor = doctorOptional
.orElseThrow(() -> new NotFoundException("Doctor Not Found"));
DoctorDto doctorDto = DoctorDtoMapper.toDto(doctor);
return ResponseEntity.status(HttpStatus.OK).body(doctorDto);
}
@RequestMapping(method = RequestMethod.POST,
produces = "application/xml", consumes = "application/xml")
public ResponseEntity<DoctorDto> createDoctor(@RequestBody DoctorDto doctorInput) {
Doctor doctor = DoctorDtoMapper.toEntity(doctorInput);
doctorService.addDoctor(doctor);
DoctorDto doctorDto = DoctorDtoMapper.toDto(doctor);
return ResponseEntity.status(HttpStatus.CREATED).body(doctorDto);
}
}
What is MockMvc
MockMvc allows to test REST Controller mechanics without starting the Web Server. We want to make the test execute as quickly as possible and avoiding to start the server saves a substantial amount of time. This is the main advantage of MockMvc testing.
With the help of Mock Mvc we’re also avoiding full Spring Context creation. The class will handle the HTTP request and pass it further to our controller. Controller code will be executed in exactly the same way as if it was processing the real request, just without the cost of starting a web server.
MockMvc also carries several useful methods for performing requests and asserting on results.
MockMvc unit test – an example
Here’s an example of building a test class with MockMvc. This class will test the DoctorController visible above.
public class DoctorControllerTest {
private DoctorController doctorController;
private DoctorService doctorService;
private MockMvc mockMvc;
public DoctorControllerTest() {
doctorService = Mockito.mock(DoctorService.class);
doctorController = new DoctorController(doctorService);
}
@Before
public void setUp() {
this.mockMvc = MockMvcBuilders.standaloneSetup(doctorController)
.build();
}
}
Our REST Controller uses DoctorService to do its job. Since we’re testing the controller, rather than the service, we’ll mock the service with Mockito and pass as a dependency to DoctorController. DoctorController is instantiated as a regular POJO class in a test class constructor.
The setUp() method configures MockMvc with a fresh standalone setup of tested REST service. MockMvcBuilders.standaloneSetup allows to test a single controller at a time, building the DispatcherServlet and all the necessary infrastructure behind.
Let’s now test what’s under controller’s GET method with followinig methods.
@Test
public void whenDoctorNotExists_thenHttp400() throws Exception {
Mockito.doReturn(Optional.empty())
.when(doctorService)
.getById(1L);
mockMvc.perform(MockMvcRequestBuilders.get("/doctors/1"))
.andExpect(MockMvcResultMatchers.status().is(HttpStatus.NOT_FOUND.value()));
}
@Test
public void whenDoctorExists_thenHttp200_andDoctorReturned() throws Exception {
String doctorName = "Doctor";
Doctor testDoctor = new Doctor(doctorName);
Mockito.doReturn(Optional.of(testDoctor))
.when(doctorService)
.getById(1L);
mockMvc.perform(MockMvcRequestBuilders.get("/doctors/1"))
.andExpect(MockMvcResultMatchers.status().is(HttpStatus.OK.value()))
.andExpect(MockMvcResultMatchers.content().string(containsString(doctorName)));
}
The above two methods test a success scenario as well as a situation when a doctor with given id does not exist. That’s pretty much all we have as all other logic is performed by DoctorService.
MockMvc allows to specify the type of request we want to send and the response we expect.
MockMvcResultMatchers is a satic factory class assisting in asserting the output. It allows to check on content, cookies, headers, status etc. It is also equiped with methods inspecting the output JSON or XML.
Lets now test the creation of a new Doctor resource with HTTP POST
@Test
public void whenPostDoctor_thenAddDoctorCalled_andDoctorReturned() throws Exception {
String newDoctor = "<doctorDto><name>Doctor John</name></doctorDto>";
mockMvc.perform(MockMvcRequestBuilders.post("/doctors")
.content(newDoctor)
.header("Content-Type","application/xml"))
.andExpect(MockMvcResultMatchers.status().is(HttpStatus.CREATED.value()))
.andExpect(MockMvcResultMatchers.xpath("/doctorDto/name", "%s")
.string(containsString("Doctor John")));
}
This test asserts that a passed DoctorDto object results in creation of new Doctor resource. It also checks whether Doctor’s name is contained in the response.
Here’s some more REST goodies