Writing Unit Tests for a REST API - Configuration

After we have finished this lesson, we:

  • Know how we can get the required dependencies with Maven and Gradle.
  • Can identify the minimum setup that we can use when we write unit tests for a REST API.
  • Understand how we can configure the system under test to use our minimum setup.

Getting the Required Dependencies

Because we will use JSONPath for writing assertions for the returned response body, we have to declare the json-path dependency in our build script.

If we we are using Maven, we have to add the following snippet into our pom.xml file:

1<dependency>
2	<groupId>com.jayway.jsonpath</groupId>
3	<artifactId>json-path</artifactId>
4	<version>2.0.0</version>
5	<scope>test</scope>
6</dependency>

If we are using Gradle, we have to add the following snippet into our build.gradle file:

1testCompile(
2		'com.jayway.jsonpath:json-path:2.0.0'
3)

Let’s move on and find out what components we should include in the system under test.

What Components Do We Need?

As I mentioned earlier , we should minimize the number of custom components that we include in the system under test. Here is a list that contains the basic components that I typically use when I am writing units tests for a REST API:

  • I add a custom @ControllerAdvice class into the system under test because I want to ensure that the thrown exceptions are handled properly.
  • I configure a custom HttpMessageConverter if I want to use a custom ObjectMapper for transforming JSON into objects and vice versa.
  • I specify the used LocaleResolver if a Locale object is injected into a method of the tested controller as a method parameter.

Important Note:

  • We don’t configure a custom LocaleResolver because our controllers don’t use it. If you want to know how you can configure it, you should take look at the lesson: Writing Unit Tests for “Normal” Controllers - Configuration .
  • We don’t have to configure a custom HttpMessageConverter because our tests don’t require it. However, I will configure it here because I want to demonstrate how you can configure it.
  • If you want to include other components in the system under test, feel free to do so. My list is quite short and I understand that you might have different requirements. Do not take this list literally! :)

Next, we will find out how we can create and configure the required components.

Creating the Required Components

We can create the required components by adding new static factory methods into the WebTestConfig class. To be more specific, we have to add the following methods into the WebTestConfig class:

  • The objectMapper() method creates and configures a new ObjectMapper object. This method is public because we will use it when we create the JSON document that is send to tested controller method.
  • The objectMapperHttpMessageConverter() method creates a new MappingJackson2HttpMessageConverter object that can read and write JSON by using Jackson.

After we have added these factory methods into our object mother class, its source code looks as follows:

 1import com.fasterxml.jackson.databind.ObjectMapper;
 2import com.fasterxml.jackson.databind.SerializationFeature;
 3import com.fasterxml.jackson.datatype.jdk8.Jdk8Module;
 4import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
 5
 6public final class WebTestConfig {
 7
 8    private WebTestConfig() {}
 9
10    public static MappingJackson2HttpMessageConverter objectMapperHttpMessageConverter() {
11        MappingJackson2HttpMessageConverter converter =
12				new MappingJackson2HttpMessageConverter();
13        converter.setObjectMapper(objectMapper());
14        return converter;
15    }
16
17    public static ObjectMapper objectMapper() {
18        return new ObjectMapper()
19                .disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)
20                .registerModule(new Jdk8Module());
21    }
22}

Let’s find out how we can include these components in the system under test.

Configuring the System Under Test

Before we can configure the system under test by using the standalone configuration, we have to identify the dependencies of the tested controller.

Let’s assume that we are writing unit tests for the TaskCrudController class whose source code looks as follows:

 1import org.springframework.beans.factory.annotation.Autowired;
 2import org.springframework.web.bind.annotation.RequestMapping;
 3import org.springframework.web.bind.annotation.RestController;
 4
 5@RestController
 6@RequestMapping("/api/task")
 7public class TaskCrudController {
 8
 9    private final TaskCrudService crudService;
10
11    @Autowired
12    TaskCrudController(TaskCrudService crudService) {
13        this.crudService = crudService;
14    }
15
16	//The other methods are not relevant yet
17}

As we can see, this controller has only one dependency. The TaskCrudService object provides CRUD operations for tasks.

After we have identified the dependencies of the tested controller, we have to create these dependencies in the setup method that is invoked before a test method is invoked. We can do this by following these steps:

  1. Add a TaskCrudService field into our test class.
  2. Add a setup method into our test class. We have to create the dependencies of the tested controller in this method.
  3. Create a new TaskCrudService mock and set the created mock object to the TaskCrudService field.

After we have created the dependencies of the tested controller, the source code of our test class looks as follows:

 1import de.bechte.junit.runners.context.HierarchicalContextRunner;
 2import org.junit.Before;
 3import org.junit.experimental.categories.Category;
 4import org.junit.runner.RunWith;
 5
 6@RunWith(HierarchicalContextRunner.class)
 7@Category(UnitTest.class)
 8public class TaskCrudControllerTest {
 9
10    private TaskCrudService crudService;
11
12    @Before
13    public void configureSystemUnderTest() {
14        crudService = mock(TaskCrudService.class);
15    }
16}

Our next step is to create a new MockMvc object and configure the components that we want to include in the system under test. However, before we can do this, we have to figure out what components are required by our tests. Let’s go through the required components one by one:

  • The TaskErrorHandler class is an error handler class that is annotated with the @ControllerAdvice annotation. We need it because it returns the correct HTTP status code when a task is not found and it handles validation errors.
  • We need a custom HttpMessageConverter because we want that our unit tests use our custom ObjectMapper.

After we have created a new MockMvc object and included the additional components in the system under test, the source code of our test class looks as follows:

 1import de.bechte.junit.runners.context.HierarchicalContextRunner;
 2import org.junit.Before;
 3import org.junit.experimental.categories.Category;
 4import org.junit.runner.RunWith;
 5import org.springframework.test.web.servlet.MockMvc;
 6import org.springframework.test.web.servlet.setup.MockMvcBuilders;
 7
 8import static com.testwithspring.starter.spring.web.WebTestConfig.objectMapperHttpMessageConverter;
 9
10@RunWith(HierarchicalContextRunner.class)
11@Category(UnitTest.class)
12public class TaskCrudControllerTest {
13
14    private TaskCrudService crudService;
15    private MockMvc mockMvc;
16
17    @Before
18    public void configureSystemUnderTest() {
19        crudService = mock(TaskCrudService.class);
20
21		TaskCrudController controller = new TaskCrudController(crudService);
22        mockMvc = MockMvcBuilders.standaloneSetup(controller)
23                .setControllerAdvice(new TaskErrorHandler())
24                .setMessageConverters(objectMapperHttpMessageConverter())
25                .build();
26    }
27}

It is important to understand that we can register other components as well. That is why I recommend that you take a look at the Javadoc of the StandaloneMockMvcBuilder class.

Let’s summarize what we learned from this lesson.

Summary

This lesson has taught us four things:

  • If we want to use JSONPath for writing assertions for the returned response body, we have to declare the json-path dependency in our build script.
  • The minimum setup that I typically use contains a custom @ControllerAdvice class and a custom ObjectMapper.
  • We can include a custom @ControllerAdvice class in the system under test by using the setControllerAdvice() method of the StandaloneMockMvcBuilder class.
  • If we want to use a custom ObjectMapper, we have to create a new MappingJackson2HttpMessageConverter object and add it into the system under test by using the setMessageConverters() method of the StandaloneMockMvcBuilder class.

Get the source code from Github