Kumar Pratyush
May 19, 2019 · 4 min read

Unit testing your API Controllers — A code walkthrough

Image for post
Image for post

In the previous blog we talked about why you should be testing your API controllers with the common pitfalls that come along with it. Now, theory without a code walkthrough is like fool’s gold. Beautiful to look at, but of no relevance. (Nerd fact : The element Pyrite is commonly known as Fool’s gold)

So we ended that blog mentioning that we would follow up with a code walkthrough and voila! Here we are! Let’s get to the meat of the matter then.

For the purpose of this blog, we have created a basic spring-boot based project. It’s an employee management system (a bare extraction of the supposed mammoth project). A DAO to store the employees in the organisation. A client interacts with the DAO to check the details of the employee add CRUD operations for the same.

Let’s start with the employee DAO and the employee POJO.

@Entity
@Builder
@Data
public class Employee {

@Id
@GeneratedValue
private Long id;
private String name;
private int level;
private String role;
private long salary;
}

Then we add a service implementation to fetch the employee details. Here, we have stubbed the db connection logic and hard-coding an employee for test purpose.

@Override
public Employee getEmployeeById(long id) {

Employee employee = null;

//Here goes your db connector which is not really important to us since we will be mocking it out
//Hence we are putting dummy condition here to write our tests
if (id == 1L) {
employee = Employee.builder()
.id(1L)
.name("quiQUA")
.level(1)
.role("FTE")
.build();
} else {
throw new EmployeeDoesNotExistException(id);
}
return employee;
}

Finally, we add a controller to fetch the employee details via API :

public ResponseEntity<Employee> getEmployeeById(@PathVariable long id) {

Employee employee = employeeService.getEmployeeById(id);
return ResponseEntity.status(HttpStatus.ACCEPTED)
.contentType(MediaType.APPLICATION_JSON)
.body(employee);
}

So we have an API which returns the employee details if the employee exists, otherwise throws an exception. Let’s see how we can test the same by using @webMvcTest annotation provided by spring.

Let’s add a test file to test our controller. We will name this class as EmployeeControllerTests.

@RunWith(SpringRunner.class)
@WebMvcTest
public class EmployeeControllerTests {

@Autowired
private MockMvc mockMvc;
}

Here we set jUnit’s default runner to run the tests with @RunWith annotation provided by spring. Next, @webMvcTest annotation tells the class to disable full auto-configuration and instead apply only configuration relevant to MVC tests.

Now we are all set to add tests. Let’s add our first happy test case.

@Test
public void shouldReturnEmployeeIfExists() throws Exception {

Employee employee = Employee.builder()
.id(1L)
.name("quiqua")
.salary(10000)
.build();

Mockito.when(employeeService.getEmployeeById(1)).thenReturn(employee);

this.mockMvc.perform(get("/employee/1"))
.andDo(print())
.andExpect(status().is2xxSuccessful())
.andExpect(MockMvcResultMatchers.jsonPath("$.id")
.value(1))
.andExpect(MockMvcResultMatchers.jsonPath("$.name")
.value("quiqua"));
}

In above test, we first create a dummy employee object to stub out the service layer call. We are just bothered about the controllers. Next, with mockMvc, we perform an API call and assert the response. Here, we are asserting the status code and the response body itself. On the similar lines, you can add assertions for headers, content-type, errors, forward URLs, redirect URLs. This really opens up new dimensions for testing the logic in controllers with just enabling a slice of the entire project.

MockMvcResultMatchers provides a plethora of APIs to test our controller response and verify for all sorts of test cases. You should check it out for more details.

Next, let’s see one more example of how we can test any exception thrown by the controller.

@Test
public void shouldThrowEmployeeDoesNotExistExceptionWhenNoEmployeeExists() throws Exception {

Employee employee = Employee.builder().build();
Mockito.when(employeeService.getEmployeeById(1)).thenReturn(employee);

this.mockMvc.perform(get("/employee/1"))
.andDo(print())
.andExpect(status().isNotFound())
.andExpect(MockMvcResultMatchers.content().string(""));
}

Here, we check that the status is NotFound and the body is empty. This would have been more meaningful if you are using custom error handling in your code with @ControllerAdvice annotation. I leave that to you as an exercise to dig deep.

So, we saw how we can bring up just a slice of your application and save a considerable amount of test sanity time. Then we saw how we can test the logic of our controllers or check the sanity of the APIs by just stubbing out the business logic.

The more you delve into it, the more use cases you would find based on your project structure. I implore you to play around with it and see how creative your tests can be.

I have run the entire code in my IDE and have also pushed the same in github. Please leave comments if you find this useful. Let us know what creative tests you are writing for your controllers.

quiQUA

the official quiQUA blog

Medium is an open platform where 170 million readers come to find insightful and dynamic thinking. Here, expert and undiscovered voices alike dive into the heart of any topic and bring new ideas to the surface. Learn more

Follow the writers, publications, and topics that matter to you, and you’ll see them on your homepage and in your inbox. Explore

If you have a story to tell, knowledge to share, or a perspective to offer — welcome home. It’s easy and free to post your thinking on any topic. Write on Medium

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store