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.
This lesson assumes that:
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
@ControllerAdviceclass into the system under test because I want to ensure that the thrown exceptions are handled properly. - I configure a custom
HttpMessageConverterif I want to use a customObjectMapperfor transforming JSON into objects and vice versa. - I specify the used
LocaleResolverif aLocaleobject is injected into a method of the tested controller as a method parameter.
Important Note:
- We don’t configure a custom
LocaleResolverbecause 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
HttpMessageConverterbecause 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 newObjectMapperobject. 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 newMappingJackson2HttpMessageConverterobject 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}
Additional Reading:
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:
- Add a
TaskCrudServicefield into our test class. - Add a setup method into our test class. We have to create the dependencies of the tested controller in this method.
- Create a new
TaskCrudServicemock and set the created mock object to theTaskCrudServicefield.
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
TaskErrorHandlerclass is an error handler class that is annotated with the@ControllerAdviceannotation. 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
HttpMessageConverterbecause we want that our unit tests use our customObjectMapper.
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.
Additional Reading:
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-pathdependency in our build script. - The minimum setup that I typically use contains a custom
@ControllerAdviceclass and a customObjectMapper. - We can include a custom
@ControllerAdviceclass in the system under test by using thesetControllerAdvice()method of theStandaloneMockMvcBuilderclass. - If we want to use a custom
ObjectMapper, we have to create a newMappingJackson2HttpMessageConverterobject and add it into the system under test by using thesetMessageConverters()method of theStandaloneMockMvcBuilderclass.