Writing Unit Tests for a REST API - Reading Data

After we have finished this lesson, we

  • Can write assertions for the content type of the HTTP response.
  • Know how we can write assertions for the returned JSON document by using JsonPath and Hamcrest matchers.

Scenario 1: Returning All Tasks Found From the Database

We have to write unit tests for a controller method that processes GET requests send to the path: ‘/api/task’. This method is called findAll() and it returns all tasks found from the database.

The source code of the tested controller looks as follows:

 1import org.springframework.beans.factory.annotation.Autowired;
 2import org.springframework.web.bind.annotation.RequestMapping;
 3import org.springframework.web.bind.annotation.RequestMethod;
 4import org.springframework.web.bind.annotation.RestController;
 5
 6@RestController
 7@RequestMapping("/api/task")
 8public class TaskCrudController {
 9
10    private final TaskCrudService crudService;
11
12    @Autowired
13    TaskCrudController(TaskCrudService crudService) {
14        this.crudService = crudService;
15    }
16
17    @RequestMapping(method = RequestMethod.GET)
18    public List<TaskListDTO> findAll() {
19        return crudService.findAll();
20    }
21}

If two tasks are found from the database, the findAll() method returns the following JSON document to the client:

 1[
 2	{
 3		"id": 1,
 4		"title": "title",
 5		"status": "OPEN"
 6	},
 7	{
 8		"id": 33,
 9		"title": "secondTask",
10		"status": "OPEN"
11	}
12]

Next we will write unit tests which ensure that the tested controller method is working as expected when two task are found. We can write these tests by following these steps:

First, we have to configure the system under test. Because we use the configuration that was described in the previous lesson of this topic , I won’t describe this step here.

After we have configured 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;
 9import static org.mockito.Mockito.mock;
10
11@RunWith(HierarchicalContextRunner.class)
12@Category(UnitTest.class)
13public class TaskCrudControllerTest {
14
15    private TaskCrudService crudService;
16    private MockMvc mockMvc;
17
18    @Before
19    public void configureSystemUnderTest() {
20        crudService = mock(TaskCrudService.class);
21
22        TaskCrudController controller = new TaskCrudController(crudService);
23        mockMvc = MockMvcBuilders.standaloneSetup(controller)
24                .setControllerAdvice(new TaskErrorHandler())
25                .setMessageConverters(objectMapperHttpMessageConverter())
26                .build();
27    }
28}
The TaskCrudService is actually a stub, but I use the term mock here because the other tests, which are described in the next lesson of this topic, will verify interactions that happened between the system under test and our test double.

Second, we have to create the class hierarchy that is required by our unit tests. After we have created this class hierarchy, the source code of our test class looks as follows:

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

Third, we have to configure our test cases by following these steps:

  1. Add a factory method called createTask() into the WhenTwoTasksAreFound class. This method creates a new TaskListDTO object and returns the created object.
  2. Add a setup method into WhenTwoTasksAreFound class.
  3. Implement the setup method by creating two TaskListDTO objects and ensuring that the TaskCrudService mock returns the created tasks when its findAll() method is invoked.

After we have configured our test cases, 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 java.util.Arrays;
 9
10import static com.testwithspring.starter.spring.web.WebTestConfig.objectMapperHttpMessageConverter;
11import static org.mockito.BDDMockito.given;
12import static org.mockito.Mockito.mock;
13
14@RunWith(HierarchicalContextRunner.class)
15@Category(UnitTest.class)
16public class TaskCrudControllerTest {
17
18    private TaskCrudService crudService;
19    private MockMvc mockMvc;
20
21    @Before
22    public void configureSystemUnderTest() {
23        crudService = mock(TaskCrudService.class);
24
25        TaskCrudController controller = new TaskCrudController(crudService);
26        mockMvc = MockMvcBuilders.standaloneSetup(controller)
27                .setControllerAdvice(new TaskErrorHandler())
28                .setMessageConverters(objectMapperHttpMessageConverter())
29                .build();
30    }
31
32    public class FindAll {
33
34        public class WhenTwoTasksAreFound {
35
36            @Before
37            public void returnTwoTasks() {
38                TaskListDTO first = createTask(1L,
39						"title",
40						TaskStatus.OPEN
41				);
42                TaskListDTO second = createTask(33L,
43						"secondTask",
44						TaskStatus.OPEN
45				);
46
47                given(crudService.findAll())
48						.willReturn(Arrays.asList(first, second));
49            }
50
51            private TaskListDTO createTask(Long id,
52										   String title,
53										   TaskStatus status) {
54                TaskListDTO task = new TaskListDTO();
55
56                task.setId(id);
57                task.setTitle(title);
58                task.setStatus(status);
59
60                return task;
61            }
62        }
63    }
64}

Fourth, we have to write four unit tests for the findAll() method:

  1. We have to verify that it returns the HTTP status code 200.
  2. We have to ensure that it returns the found tasks as JSON.
  3. We have to verify that it returns two tasks.
  4. We have to ensure that it returns the correct information.

After we have written these unit tests, the source code of our test class looks as follows:

  1import de.bechte.junit.runners.context.HierarchicalContextRunner;
  2import org.junit.Before;
  3import org.junit.Test;
  4import org.junit.experimental.categories.Category;
  5import org.junit.runner.RunWith;
  6import org.springframework.http.MediaType;
  7import org.springframework.test.web.servlet.MockMvc;
  8import org.springframework.test.web.servlet.setup.MockMvcBuilders;
  9
 10import java.util.Arrays;
 11
 12import static com.testwithspring.starter.spring.web.WebTestConfig.objectMapperHttpMessageConverter;
 13import static org.hamcrest.Matchers.hasSize;
 14import static org.hamcrest.Matchers.is;
 15import static org.mockito.BDDMockito.given;
 16import static org.mockito.Mockito.mock;
 17import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
 18import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
 19import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
 20import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
 21
 22@RunWith(HierarchicalContextRunner.class)
 23@Category(UnitTest.class)
 24public class TaskCrudControllerTest {
 25
 26    private TaskCrudService crudService;
 27    private MockMvc mockMvc;
 28
 29    @Before
 30    public void configureSystemUnderTest() {
 31        crudService = mock(TaskCrudService.class);
 32
 33        TaskCrudController controller = new TaskCrudController(crudService);
 34        mockMvc = MockMvcBuilders.standaloneSetup(controller)
 35                .setControllerAdvice(new TaskErrorHandler())
 36                .setMessageConverters(objectMapperHttpMessageConverter())
 37                .build();
 38    }
 39
 40    public class FindAll {
 41
 42        public class WhenTwoTasksAreFound {
 43
 44            @Before
 45            public void returnTwoTasks() {
 46                TaskListDTO first = createTask(1L,
 47                        "title",
 48                        TaskStatus.OPEN
 49                );
 50                TaskListDTO second = createTask(33L,
 51                        "secondTask",
 52                        TaskStatus.OPEN
 53                );
 54
 55                given(crudService.findAll())
 56                        .willReturn(Arrays.asList(first, second));
 57            }
 58
 59            private TaskListDTO createTask(Long id,
 60                                           String title,
 61                                           TaskStatus status) {
 62                TaskListDTO task = new TaskListDTO();
 63
 64                task.setId(id);
 65                task.setTitle(title);
 66                task.setStatus(status);
 67
 68                return task;
 69            }
 70
 71            @Test
 72            public void shouldReturnHttpStatusCodeOk() throws Exception {
 73                mockMvc.perform(get("/api/task"))
 74                        .andExpect(status().isOk());
 75            }
 76
 77            @Test
 78            public void shouldReturnFoundTaskAsJson() throws Exception {
 79                mockMvc.perform(get("/api/task"))
 80                        .andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8));
 81            }
 82
 83            @Test
 84            public void shouldReturnTwoTasks() throws Exception {
 85                mockMvc.perform(get("/api/task"))
 86                        .andExpect(jsonPath("$", hasSize(2)));
 87            }
 88
 89            @Test
 90            public void shouldReturnCorrectInformation() throws Exception {
 91                mockMvc.perform(get("/api/task"))
 92                        .andExpect(jsonPath("$[0].id", is(1)))
 93                        .andExpect(jsonPath("$[0].title", is("title")))
 94						.andExpect(jsonPath("$[0].status", is(TaskStatus.OPEN.toString())))
 95                        .andExpect(jsonPath("$[1].id", is(33)))
 96                        .andExpect(jsonPath("$[1].title", is("secondTask")))
 97						.andExpect(jsonPath("$[1].status", is(TaskStatus.OPEN.toString())));
 98            }
 99        }
100    }
101}

Let’s move on and write unit tests for a controller method that returns the information of a single task.

Scenario 2: Returning the Information of a Single Task

We have to write unit tests for a controller method that processes GET requests send to the path: ‘/api/task/{taskId}’. The name of this method is findById() and it simply returns the information of the task that is found from the database.

The source code of the tested controller method looks as follows:

 1import org.springframework.beans.factory.annotation.Autowired;
 2import org.springframework.web.bind.annotation.PathVariable;
 3import org.springframework.web.bind.annotation.RequestMapping;
 4import org.springframework.web.bind.annotation.RequestMethod;
 5import org.springframework.web.bind.annotation.RestController;
 6
 7@RestController
 8@RequestMapping("/api/task")
 9public class TaskCrudController {
10
11    private final TaskCrudService crudService;
12
13    @Autowired
14    TaskCrudController(TaskCrudService crudService) {
15        this.crudService = crudService;
16    }
17
18    @RequestMapping(value = "{taskId}", method = RequestMethod.GET)
19    public TaskDTO findById(@PathVariable("taskId") Long taskId) {
20        return crudService.findById(taskId);
21    }
22}

If an open task is found from the database, the findById() method returns the following JSON document to the client:

 1{
 2	"assigneeId":null,
 3	"closerId":null,
 4	"creatorId":99,
 5	"description":"description",
 6	"id":1,
 7	"resolution":null,
 8	"status":"OPEN",
 9	"title":"title"
10}

Next, we will write unit tests which ensure that the findById() method is working as expected when the requested task is found. We can write these tests by following these steps:

First, we have to add the required class hierarchy into the TaskCrudControllerTest class. After we have added this class hierarchy into our test class, its source code 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;
 9import static org.mockito.Mockito.mock;
10
11@RunWith(HierarchicalContextRunner.class)
12@Category(UnitTest.class)
13public class TaskCrudControllerTest {
14
15    private TaskCrudService crudService;
16    private MockMvc mockMvc;
17
18    @Before
19    public void configureSystemUnderTest() {
20        crudService = mock(TaskCrudService.class);
21
22        TaskCrudController controller = new TaskCrudController(crudService);
23        mockMvc = MockMvcBuilders.standaloneSetup(controller)
24                .setControllerAdvice(new TaskErrorHandler())
25                .setMessageConverters(objectMapperHttpMessageConverter())
26                .build();
27    }
28
29    public class FindById {
30
31        public class WhenTaskIsFound {
32
33        }
34    }
35}

Second, we have to configure our test cases by following these steps:

  1. Add a setup method into the WhenTaskIsFound class. We will configure our test cases in this method.
  2. Create a new TaskDTO object. We will create this object by using a test data builder because this DTO is not just a data container. Its content depends from the state of the returned task and that is why it is a good idea to use a test data builder.
  3. Ensure that the TaskCrudService mock returns the created task when its findById() method is invoked and 1L is passed as a method parameter.

After we have configured our test cases, 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;
 9import static org.mockito.BDDMockito.given;
10import static org.mockito.Mockito.mock;
11
12@RunWith(HierarchicalContextRunner.class)
13@Category(UnitTest.class)
14public class TaskCrudControllerTest {
15
16    private TaskCrudService crudService;
17    private MockMvc mockMvc;
18
19    @Before
20    public void configureSystemUnderTest() {
21        crudService = mock(TaskCrudService.class);
22
23        TaskCrudController controller = new TaskCrudController(crudService);
24        mockMvc = MockMvcBuilders.standaloneSetup(controller)
25                .setControllerAdvice(new TaskErrorHandler())
26                .setMessageConverters(objectMapperHttpMessageConverter())
27                .build();
28    }
29
30    public class FindById {
31
32        public class WhenTaskIsFound {
33
34            @Before
35            public void returnFoundTask() {
36                TaskDTO found = new TaskDTOBuilder()
37                        .withId(1L)
38                        .withCreator(99L)
39                        .withTitle("title")
40                        .withDescription("description")
41                        .withStatusOpen()
42                        .build();
43
44                given(crudService.findById(1L)).willReturn(found);
45            }
46        }
47    }
48}

Third, we have to write three unit tests for the findById() method:

  1. We have to verify that it returns the HTTP status code 200.
  2. We have to ensure that it returns the information of the found task as JSON.
  3. We have verify that it returns the correct information.

After we have written these unit tests, the source code of our test class looks as follows:

 1import de.bechte.junit.runners.context.HierarchicalContextRunner;
 2import org.junit.Before;
 3import org.junit.Test;
 4import org.junit.experimental.categories.Category;
 5import org.junit.runner.RunWith;
 6import org.springframework.http.MediaType;
 7import org.springframework.test.web.servlet.MockMvc;
 8import org.springframework.test.web.servlet.setup.MockMvcBuilders;
 9
10import static com.testwithspring.starter.spring.web.WebTestConfig.objectMapperHttpMessageConverter;
11import static org.hamcrest.Matchers.is;
12import static org.hamcrest.Matchers.nullValue;
13import static org.mockito.BDDMockito.given;
14import static org.mockito.Mockito.mock;
15import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
16import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
17import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
18import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
19
20@RunWith(HierarchicalContextRunner.class)
21@Category(UnitTest.class)
22public class TaskCrudControllerTest {
23
24    private TaskCrudService crudService;
25    private MockMvc mockMvc;
26
27    @Before
28    public void configureSystemUnderTest() {
29        crudService = mock(TaskCrudService.class);
30
31        TaskCrudController controller = new TaskCrudController(crudService);
32        mockMvc = MockMvcBuilders.standaloneSetup(controller)
33                .setControllerAdvice(new TaskErrorHandler())
34                .setMessageConverters(objectMapperHttpMessageConverter())
35                .build();
36    }
37
38    public class FindById {
39
40        public class WhenTaskIsFound {
41
42            @Before
43            public void returnFoundTask() {
44                TaskDTO found = new TaskDTOBuilder()
45                        .withId(1L)
46                        .withCreator(99L)
47                        .withTitle("title")
48                        .withDescription("description")
49                        .withStatusOpen()
50                        .build();
51
52                given(crudService.findById(1L)).willReturn(found);
53            }
54
55            @Test
56            public void shouldReturnHttpStatusCodeOk() throws Exception {
57                mockMvc.perform(get("/api/task/{taskId}", 1L))
58                        .andExpect(status().isOk());
59            }
60
61            @Test
62            public void shouldReturnFoundTaskAsJson() throws Exception {
63                mockMvc.perform(get("/api/task/{taskId}", 1L))
64                        .andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8));
65            }
66
67            @Test
68            public void shouldReturnCorrectInformation() throws Exception {
69                mockMvc.perform(get("/api/task/{taskId}", 1L))
70                        .andExpect(jsonPath("$.assigneeId", nullValue()))
71                        .andExpect(jsonPath("$.closerId", nullValue()))
72                        .andExpect(jsonPath("$.creatorId", is(99)))
73                        .andExpect(jsonPath("$.id", is(1)))
74                        .andExpect(jsonPath("$.title", is("title")))
75                        .andExpect(jsonPath("$.description", is("description")))
76                        .andExpect(jsonPath("$.status", is(TaskStatus.OPEN.toString())))
77                        .andExpect(jsonPath("$.resolution", nullValue()));
78            }
79        }
80    }
81}

Let’s summarize what we learned from this lesson.

Summary

This lesson has taught us two things:

  • We can write assertions for the content type of the HTTP response by using the ContentResultMatchers class.
  • We can write assertions for the content of the returned JSON document by using the JsonPathResultMatchers class.

Get the source code from Github