Writing Integration Tests for a REST API - Reading Data

After we have finished this lesson, we

  • Understand what kind of test data we should use when we write integration tests for controller methods that find information from the database.
  • Know how we can write integration tests for controller methods that return the data found from the database as JSON documents.

Use Case 1: Returning the Information of a Single Task

Introduction to System Under Test

We have to write integration 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 a closed task is found from the database, the findById() method returns the following JSON document to the client:

 1{
 2	"assigneeId":1,
 3	"closerId":1,
 4	"creationTime":"2016-12-03T11:41:28+02:00[Europe/Helsinki]",
 5	"creatorId":1,
 6	"description":"This example contains integration tests",
 7	"id":1,
 8	"modificationTime":"2016-12-03T11:41:28+02:00[Europe/Helsinki]",
 9	"resolution":"DONE",
10	"status":"CLOSED",
11	"tags":[
12		{
13			"id":1,
14			"name":"example"
15		}
16	],
17	"title":"Write example application"
18}

Let’s move on and find out how we can create the DbUnit data set that initializes our database into a known state before our integration tests are run.

Initializing Our Database Into a Known State

We will initialize our database into a known state by using the data set and the constant class which we created in the lesson: Writing Integration Tests for “Normal” Controllers - Reading Data .

The name of our DbUnit data set is tasks.xml, and its source code looks as follows:

 1<dataset>
 2    <tasks id="1"
 3           assignee_id="1"
 4           closer_id="1"
 5           creation_time="2016-12-3 11:41:28"
 6           creator_id="1"
 7           description="This example contains integration tests"
 8           modification_time="2016-12-3 11:41:28"
 9           resolution="DONE"
10           status="CLOSED"
11           title="Write example application"
12           version="0"/>
13    <tasks id="2"
14           assignee_id="[null]"
15           closer_id="[null]"
16           creation_time="2016-12-4 11:41:28"
17           creator_id="1"
18           description="This lesson talks about integration testing"
19           modification_time="2016-12-4 11:41:28"
20           resolution="[null]"
21           status="OPEN"
22           title="Write lesson"
23           version="0"/>
24
25    <tags id="1"
26          creation_time="2016-12-3 11:41:28"
27          modification_time="2016-12-3 11:41:28"
28          name="example"
29          version="0"/>
30    <tags id="2"
31          creation_time="2016-12-3 11:41:28"
32          modification_time="2016-12-3 11:41:28"
33          name="lesson"
34          version="0"/>
35
36    <tasks_tags task_id="1" tag_id="1"/>
37    <tasks_tags task_id="2" tag_id="2"/>
38</dataset>

Because our REST API returns the creation and modification time as timestamp strings, we have to make the following changes to the constant class that contains the information of the tasks found from our database:

  1. Create a method that extracts the timezone information from the system’s default timezone and adds this information to the timestamp string given as a method parameter. Also, this method must return the created timestamp string. The goal of this method is to ensure that we can run our integration tests in different time zones without making any changes to our test data.
  2. Declare the constants which contain the creation time and the modication time of our tasks. Remember to use our new utility method.

After we have made the required changes to the Tasks class, its source code looks as follows:

 1import java.time.ZoneId;
 2import java.time.ZonedDateTime;
 3
 4public final class Tasks {
 5
 6    private Tasks() {}
 7
 8    public static final String SEARCH_TERM_ONE_MATCH = "Esso";
 9    public static final String SEARCH_TERM_NOT_FOUND = "NO_PE";
10
11    public static class WriteExampleApp {
12
13        public static final Long ASSIGNEE_ID = 1L;
14        public static final Long CLOSER_ID = 1L;
15        public static final String CREATION_TIME_STRING = getDateTimeWithZoneOffsetAndId("2016-12-03T11:41:28");
16        public static final Long CREATOR_ID = 1L;
17        public static final String DESCRIPTION = "This example contains integration tests";
18        public static final Long ID = 1L;
19        public static final String MODIFICATION_TIME_STRING = getDateTimeWithZoneOffsetAndId("2016-12-03T11:41:28");
20        public static final TaskResolution RESOLUTION = TaskResolution.DONE;
21        public static final TaskStatus STATUS = TaskStatus.CLOSED;
22        public static final String TITLE = "Write example application";
23
24        public static class Tags {
25
26            public static class Example {
27
28                public static final Long ID = 1L;
29                public static final String NAME = "example";
30            }
31        }
32    }
33
34    public static class WriteLesson {
35
36        public static final String CREATION_TIME_STRING = getDateTimeWithZoneOffsetAndId("2016-12-04T11:41:28");
37        public static final Long CREATOR_ID = 1L;
38        public static final String DESCRIPTION = "This lesson talks about integration testing";
39        public static final Long ID = 2L;
40        public static final String MODIFICATION_TIME_STRING = getDateTimeWithZoneOffsetAndId("2016-12-04T11:41:28");
41        public static final TaskStatus STATUS = TaskStatus.OPEN;
42        public static final String TITLE = "Write lesson";
43
44        public static class Tags {
45
46            public static class Lesson {
47
48                public static final Long ID = 2L;
49                public static final String NAME = "lesson";
50            }
51        }
52    }
53
54    private static String getDateTimeWithZoneOffsetAndId(String dateTime) {
55        dateTime += getSystemZoneOffset();
56        dateTime += getSystemZoneId();
57        return dateTime;
58    }
59
60    private static String getSystemZoneOffset() {
61        return ZonedDateTime.now().getOffset().toString();
62    }
63
64    private static String getSystemZoneId() {
65        return "[" + ZoneId.systemDefault().toString() + "]";
66    }
67}

After we have created our DbUnit data set, we can write integration tests for the tested controller method.

Writing Our Integration Tests

Before we can write the actual integration tests, we have to add test specific configuration into our test class. We can configure our test cases by following these steps:

  1. Configure the DbUnit data set that initializes our database into a known state. Because we are writing integration tests for a finder method, all test methods can use the same data set.
  2. Create a private method that sends a GET request to the path: ‘/api/task/1’. This method returns a ResultActions object, and its goal is to help us to reduce duplicate code which is often found from test classes that use the Spring MVC Test framework.

After we have configured our test cases, the source code of our test class looks as follows:

 1import com.github.springtestdbunit.DbUnitTestExecutionListener;
 2import com.github.springtestdbunit.annotation.DatabaseSetup;
 3import com.github.springtestdbunit.annotation.DbUnitConfiguration;
 4import org.junit.Before;
 5import org.junit.experimental.categories.Category;
 6import org.junit.runner.RunWith;
 7import org.springframework.beans.factory.annotation.Autowired;
 8import org.springframework.test.context.ActiveProfiles;
 9import org.springframework.test.context.ContextConfiguration;
10import org.springframework.test.context.TestExecutionListeners;
11import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
12import org.springframework.test.context.support.DependencyInjectionTestExecutionListener;
13import org.springframework.test.context.transaction.TransactionalTestExecutionListener;
14import org.springframework.test.context.web.ServletTestExecutionListener;
15import org.springframework.test.context.web.WebAppConfiguration;
16import org.springframework.test.web.servlet.MockMvc;
17import org.springframework.test.web.servlet.ResultActions;
18import org.springframework.test.web.servlet.setup.MockMvcBuilders;
19import org.springframework.web.context.WebApplicationContext;
20
21import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
22
23@RunWith(SpringJUnit4ClassRunner.class)
24@ContextConfiguration(classes = {IntegrationTestContext.class})
25@WebAppConfiguration
26@TestExecutionListeners({
27        DependencyInjectionTestExecutionListener.class,
28        TransactionalTestExecutionListener.class,
29        DbUnitTestExecutionListener.class,
30        ServletTestExecutionListener.class
31})
32@DatabaseSetup("/com/testwithspring/intermediate/tasks.xml")
33@DbUnitConfiguration(dataSetLoader = ReplacementDataSetLoader.class)
34@Category(IntegrationTest.class)
35@ActiveProfiles(Profiles.INTEGRATION_TEST)
36public class FindTaskWhenTaskIsFoundTest {
37
38    @Autowired
39    private WebApplicationContext webAppContext;
40
41    private MockMvc mockMvc;
42
43    @Before
44    public void configureSystemUnderTest() {
45        mockMvc = MockMvcBuilders.webAppContextSetup(webAppContext)
46                .build();
47    }
48
49    private ResultActions findTask() throws Exception {
50        return mockMvc.perform(get("/api/task/{taskId}",
51                Tasks.WriteExampleApp.ID
52        ));
53    }
54}
If you don’t know how you can configure your integration tests, you should take a look at the Configuring Our Integration Tests topic.

After we have configured our test cases, we have to write five integration tests for the findById() method. These tests ensure that the tested controller method is working as expected when the requested task is found.

First, we have to verify that it returns the HTTP status code 200. After we have written this integration test, the source code of our test class looks as follows:

 1import com.github.springtestdbunit.DbUnitTestExecutionListener;
 2import com.github.springtestdbunit.annotation.DatabaseSetup;
 3import com.github.springtestdbunit.annotation.DbUnitConfiguration;
 4import org.junit.Before;
 5import org.junit.Test;
 6import org.junit.experimental.categories.Category;
 7import org.junit.runner.RunWith;
 8import org.springframework.beans.factory.annotation.Autowired;
 9import org.springframework.test.context.ActiveProfiles;
10import org.springframework.test.context.ContextConfiguration;
11import org.springframework.test.context.TestExecutionListeners;
12import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
13import org.springframework.test.context.support.DependencyInjectionTestExecutionListener;
14import org.springframework.test.context.transaction.TransactionalTestExecutionListener;
15import org.springframework.test.context.web.ServletTestExecutionListener;
16import org.springframework.test.context.web.WebAppConfiguration;
17import org.springframework.test.web.servlet.MockMvc;
18import org.springframework.test.web.servlet.ResultActions;
19import org.springframework.test.web.servlet.setup.MockMvcBuilders;
20import org.springframework.web.context.WebApplicationContext;
21
22import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
23import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
24
25@RunWith(SpringJUnit4ClassRunner.class)
26@ContextConfiguration(classes = {IntegrationTestContext.class})
27@WebAppConfiguration
28@TestExecutionListeners({
29        DependencyInjectionTestExecutionListener.class,
30        TransactionalTestExecutionListener.class,
31        DbUnitTestExecutionListener.class,
32        ServletTestExecutionListener.class
33})
34@DatabaseSetup("/com/testwithspring/intermediate/tasks.xml")
35@DbUnitConfiguration(dataSetLoader = ReplacementDataSetLoader.class)
36@Category(IntegrationTest.class)
37@ActiveProfiles(Profiles.INTEGRATION_TEST)
38public class FindTaskWhenTaskIsFoundTest {
39
40    @Autowired
41    private WebApplicationContext webAppContext;
42
43    private MockMvc mockMvc;
44
45    @Before
46    public void configureSystemUnderTest() {
47        mockMvc = MockMvcBuilders.webAppContextSetup(webAppContext)
48                .build();
49    }
50
51    @Test
52    public void shouldReturnHttpStatusCodeOk() throws Exception {
53        findTask()
54                .andExpect(status().isOk());
55    }
56
57    private ResultActions findTask() throws Exception {
58        return mockMvc.perform(get("/api/task/{taskId}",
59                Tasks.WriteExampleApp.ID
60        ));
61    }
62}

Second, we have to ensure that it returns the found task as JSON. After we have implemented this integration test, the relevant part of our test class looks as follows:

 1import com.github.springtestdbunit.DbUnitTestExecutionListener;
 2import com.github.springtestdbunit.annotation.DatabaseSetup;
 3import com.github.springtestdbunit.annotation.DbUnitConfiguration;
 4import org.junit.Before;
 5import org.junit.Test;
 6import org.junit.experimental.categories.Category;
 7import org.junit.runner.RunWith;
 8import org.springframework.beans.factory.annotation.Autowired;
 9import org.springframework.http.MediaType;
10import org.springframework.test.context.ActiveProfiles;
11import org.springframework.test.context.ContextConfiguration;
12import org.springframework.test.context.TestExecutionListeners;
13import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
14import org.springframework.test.context.support.DependencyInjectionTestExecutionListener;
15import org.springframework.test.context.transaction.TransactionalTestExecutionListener;
16import org.springframework.test.context.web.ServletTestExecutionListener;
17import org.springframework.test.context.web.WebAppConfiguration;
18import org.springframework.test.web.servlet.MockMvc;
19import org.springframework.test.web.servlet.ResultActions;
20import org.springframework.test.web.servlet.setup.MockMvcBuilders;
21import org.springframework.web.context.WebApplicationContext;
22
23import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
24import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
25
26@RunWith(SpringJUnit4ClassRunner.class)
27@ContextConfiguration(classes = {IntegrationTestContext.class})
28@WebAppConfiguration
29@TestExecutionListeners({
30        DependencyInjectionTestExecutionListener.class,
31        TransactionalTestExecutionListener.class,
32        DbUnitTestExecutionListener.class,
33        ServletTestExecutionListener.class
34})
35@DatabaseSetup("/com/testwithspring/intermediate/tasks.xml")
36@DbUnitConfiguration(dataSetLoader = ReplacementDataSetLoader.class)
37@Category(IntegrationTest.class)
38@ActiveProfiles(Profiles.INTEGRATION_TEST)
39public class FindTaskWhenTaskIsFoundTest {
40
41    @Autowired
42    private WebApplicationContext webAppContext;
43
44    private MockMvc mockMvc;
45
46    @Before
47    public void configureSystemUnderTest() {
48        mockMvc = MockMvcBuilders.webAppContextSetup(webAppContext)
49                .build();
50    }
51
52    @Test
53    public void shouldReturnFoundTaskAsJson() throws Exception {
54        findTask()
55                .andExpect(
56                        content().contentType(MediaType.APPLICATION_JSON_UTF8)
57                );
58    }
59
60    private ResultActions findTask() throws Exception {
61        return mockMvc.perform(get("/api/task/{taskId}",
62                Tasks.WriteExampleApp.ID
63        ));
64    }
65}

Third, we have to verify that it returns the information of the found task. After we have written this test method, the relevant part of our integration test class looks as follows:

 1import com.github.springtestdbunit.DbUnitTestExecutionListener;
 2import com.github.springtestdbunit.annotation.DatabaseSetup;
 3import com.github.springtestdbunit.annotation.DbUnitConfiguration;
 4import org.junit.Before;
 5import org.junit.Test;
 6import org.junit.experimental.categories.Category;
 7import org.junit.runner.RunWith;
 8import org.springframework.beans.factory.annotation.Autowired;
 9import org.springframework.test.context.ActiveProfiles;
10import org.springframework.test.context.ContextConfiguration;
11import org.springframework.test.context.TestExecutionListeners;
12import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
13import org.springframework.test.context.support.DependencyInjectionTestExecutionListener;
14import org.springframework.test.context.transaction.TransactionalTestExecutionListener;
15import org.springframework.test.context.web.ServletTestExecutionListener;
16import org.springframework.test.context.web.WebAppConfiguration;
17import org.springframework.test.web.servlet.MockMvc;
18import org.springframework.test.web.servlet.ResultActions;
19import org.springframework.test.web.servlet.setup.MockMvcBuilders;
20import org.springframework.web.context.WebApplicationContext;
21
22import static org.hamcrest.Matchers.is;
23import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
24import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
25import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
26
27@RunWith(SpringJUnit4ClassRunner.class)
28@ContextConfiguration(classes = {IntegrationTestContext.class})
29//@ContextConfiguration(locations = {"classpath:integration-test-context.xml"})
30@WebAppConfiguration
31@TestExecutionListeners({
32        DependencyInjectionTestExecutionListener.class,
33        TransactionalTestExecutionListener.class,
34        DbUnitTestExecutionListener.class,
35        ServletTestExecutionListener.class
36})
37@DatabaseSetup("/com/testwithspring/intermediate/tasks.xml")
38@DbUnitConfiguration(dataSetLoader = ReplacementDataSetLoader.class)
39@Category(IntegrationTest.class)
40@ActiveProfiles(Profiles.INTEGRATION_TEST)
41public class FindTaskWhenTaskIsFoundTest {
42
43    @Autowired
44    private WebApplicationContext webAppContext;
45
46    private MockMvc mockMvc;
47
48    @Before
49    public void configureSystemUnderTest() {
50        mockMvc = MockMvcBuilders.webAppContextSetup(webAppContext)
51                .build();
52    }
53
54    @Test
55    public void shouldReturnInformationOfFoundTask() throws Exception {
56        findTask()
57                .andExpect(jsonPath("$.assigneeId",
58                        is(Tasks.WriteExampleApp.ASSIGNEE_ID.intValue())
59                ))
60                .andExpect(jsonPath("$.closerId",
61                        is(Tasks.WriteExampleApp.CLOSER_ID.intValue())
62                ))
63                .andExpect(jsonPath("$.creationTime",
64                        is(Tasks.WriteExampleApp.CREATION_TIME_STRING)
65                ))
66                .andExpect(jsonPath("$.creatorId",
67                        is(Tasks.WriteExampleApp.CREATOR_ID.intValue())
68                ))
69                .andExpect(jsonPath("$.id",
70                        is(Tasks.WriteExampleApp.ID.intValue())
71                ))
72                .andExpect(jsonPath("$.modificationTime",
73                        is(Tasks.WriteExampleApp.MODIFICATION_TIME_STRING)
74                ))
75                .andExpect(jsonPath("$.title",
76                        is(Tasks.WriteExampleApp.TITLE)
77                ))
78                .andExpect(jsonPath("$.description",
79                        is(Tasks.WriteExampleApp.DESCRIPTION)
80                ))
81                .andExpect(jsonPath("$.status",
82                        is(Tasks.WriteExampleApp.STATUS.toString())
83                ))
84                .andExpect(jsonPath("$.resolution",
85                        is(Tasks.WriteExampleApp.RESOLUTION.toString())
86                ));
87    }
88
89    private ResultActions findTask() throws Exception {
90        return mockMvc.perform(get("/api/task/{taskId}",
91                Tasks.WriteExampleApp.ID
92        ));
93    }
94}

Fourth, we have to ensure that it returns a task that has one tag. After we have written this integration test, the relevant part of our test class looks as follows:

 1import com.github.springtestdbunit.DbUnitTestExecutionListener;
 2import com.github.springtestdbunit.annotation.DatabaseSetup;
 3import com.github.springtestdbunit.annotation.DbUnitConfiguration;
 4import org.junit.Before;
 5import org.junit.Test;
 6import org.junit.experimental.categories.Category;
 7import org.junit.runner.RunWith;
 8import org.springframework.beans.factory.annotation.Autowired;
 9import org.springframework.test.context.ActiveProfiles;
10import org.springframework.test.context.ContextConfiguration;
11import org.springframework.test.context.TestExecutionListeners;
12import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
13import org.springframework.test.context.support.DependencyInjectionTestExecutionListener;
14import org.springframework.test.context.transaction.TransactionalTestExecutionListener;
15import org.springframework.test.context.web.ServletTestExecutionListener;
16import org.springframework.test.context.web.WebAppConfiguration;
17import org.springframework.test.web.servlet.MockMvc;
18import org.springframework.test.web.servlet.ResultActions;
19import org.springframework.test.web.servlet.setup.MockMvcBuilders;
20import org.springframework.web.context.WebApplicationContext;
21
22import static org.hamcrest.Matchers.hasSize;
23import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
24import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
25
26@RunWith(SpringJUnit4ClassRunner.class)
27@ContextConfiguration(classes = {IntegrationTestContext.class})
28@WebAppConfiguration
29@TestExecutionListeners({
30        DependencyInjectionTestExecutionListener.class,
31        TransactionalTestExecutionListener.class,
32        DbUnitTestExecutionListener.class,
33        ServletTestExecutionListener.class
34})
35@DatabaseSetup("/com/testwithspring/intermediate/tasks.xml")
36@DbUnitConfiguration(dataSetLoader = ReplacementDataSetLoader.class)
37@Category(IntegrationTest.class)
38@ActiveProfiles(Profiles.INTEGRATION_TEST)
39public class FindTaskWhenTaskIsFoundTest {
40
41    @Autowired
42    private WebApplicationContext webAppContext;
43
44    private MockMvc mockMvc;
45
46    @Before
47    public void configureSystemUnderTest() {
48        mockMvc = MockMvcBuilders.webAppContextSetup(webAppContext)
49                .build();
50    }
51
52    @Test
53    public void shouldReturnTaskThatHasOneTag() throws Exception {
54        findTask()
55                .andExpect(jsonPath("$.tags", hasSize(1)));
56    }
57
58    private ResultActions findTask() throws Exception {
59        return mockMvc.perform(get("/api/task/{taskId}",
60                Tasks.WriteExampleApp.ID
61        ));
62    }
63}

Fifth, we have to verify that it returns the information of the correct tag. After we have written this integration test, the source code of our test class looks as follows:

 1import com.github.springtestdbunit.DbUnitTestExecutionListener;
 2import com.github.springtestdbunit.annotation.DatabaseSetup;
 3import com.github.springtestdbunit.annotation.DbUnitConfiguration;
 4import org.junit.Before;
 5import org.junit.Test;
 6import org.junit.experimental.categories.Category;
 7import org.junit.runner.RunWith;
 8import org.springframework.beans.factory.annotation.Autowired;
 9import org.springframework.http.MediaType;
10import org.springframework.test.context.ActiveProfiles;
11import org.springframework.test.context.ContextConfiguration;
12import org.springframework.test.context.TestExecutionListeners;
13import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
14import org.springframework.test.context.support.DependencyInjectionTestExecutionListener;
15import org.springframework.test.context.transaction.TransactionalTestExecutionListener;
16import org.springframework.test.context.web.ServletTestExecutionListener;
17import org.springframework.test.context.web.WebAppConfiguration;
18import org.springframework.test.web.servlet.MockMvc;
19import org.springframework.test.web.servlet.ResultActions;
20import org.springframework.test.web.servlet.setup.MockMvcBuilders;
21import org.springframework.web.context.WebApplicationContext;
22
23import static org.hamcrest.Matchers.is;
24import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
25import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
26
27@RunWith(SpringJUnit4ClassRunner.class)
28@ContextConfiguration(classes = {IntegrationTestContext.class})
29@WebAppConfiguration
30@TestExecutionListeners({
31        DependencyInjectionTestExecutionListener.class,
32        TransactionalTestExecutionListener.class,
33        DbUnitTestExecutionListener.class,
34        ServletTestExecutionListener.class
35})
36@DatabaseSetup("/com/testwithspring/intermediate/tasks.xml")
37@DbUnitConfiguration(dataSetLoader = ReplacementDataSetLoader.class)
38@Category(IntegrationTest.class)
39@ActiveProfiles(Profiles.INTEGRATION_TEST)
40public class FindTaskWhenTaskIsFoundTest {
41
42    @Autowired
43    private WebApplicationContext webAppContext;
44
45    private MockMvc mockMvc;
46
47    @Before
48    public void configureSystemUnderTest() {
49        mockMvc = MockMvcBuilders.webAppContextSetup(webAppContext)
50                .build();
51    }
52
53    @Test
54    public void shouldReturnInformationOfCorrectTag() throws Exception {
55        findTask()
56                .andExpect(jsonPath("$.tags[0].id",
57                        is(Tasks.WriteExampleApp.Tags.Example.ID.intValue())
58                ))
59                .andExpect(jsonPath("$.tags[0].name",
60                        is(Tasks.WriteExampleApp.Tags.Example.NAME)
61                ));
62    }
63
64    private ResultActions findTask() throws Exception {
65        return mockMvc.perform(get("/api/task/{taskId}",
66                Tasks.WriteExampleApp.ID
67        ));
68    }
69}

Next, we will write integration tests for a controller method that searches tasks from the database.

Use Case 2: Searching Tasks

Introduction to System Under Test

We have to write integration tests for a controller method that processes GET requests send to the path: ‘/api/task/search’. This method is called: search(), and it is implemented by following these steps:

  1. Get the used search term from the request parameter called: searchTerm.
  2. Find the search results from the database.
  3. Return the search results as JSON.

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

 1import com.testwithspring.intermediate.task.TaskListDTO;
 2import com.testwithspring.intermediate.task.TaskSearchService;
 3import org.springframework.beans.factory.annotation.Autowired;
 4import org.springframework.web.bind.annotation.RequestMapping;
 5import org.springframework.web.bind.annotation.RequestMethod;
 6import org.springframework.web.bind.annotation.RequestParam;
 7import org.springframework.web.bind.annotation.RestController;
 8
 9import java.util.List;
10
11@RestController
12public class TaskSearchController {
13
14    private final TaskSearchService service;
15
16    @Autowired
17    TaskSearchController(TaskSearchService service) {
18        this.service = service;
19    }
20
21    @RequestMapping(value = "/api/task/search", method = RequestMethod.GET)
22    public List<TaskListDTO> search(@RequestParam("searchTerm") String searchTerm) {
23        return service.search(searchTerm);
24    }
25}

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

 1[
 2    {
 3        "id": 1,
 4        "title": "Write example application",
 5        "status": "CLOSED"
 6    },
 7    {
 8        "id": 2,
 9        "title": "Write lesson",
10        "status": "OPEN"
11    }
12]

Next, we will write integration tests which ensure that the search() method is working as expected when one task is found.

Writing Our Integration Tests

Before we can write the actual integration tests, we have to add test specific configuration into our test class. We can configure our test cases by following these steps:

  1. Configure the DbUnit data set that initializes our database into a known state. Because we are writing integration tests for a finder method, all test methods can use the same data set. Also, we will use the DbUnit data set (tasks.xml) that we used when we wrote integration tests for the findById() method.
  2. Create a private method that sends a GET request to the path: ‘/api/task/search’ and specifies the used search term. This method must return a ResultActions object.

After we have configured our test cases, the source code of our test class looks as follows:

 1import com.github.springtestdbunit.DbUnitTestExecutionListener;
 2import com.github.springtestdbunit.annotation.DatabaseSetup;
 3import com.github.springtestdbunit.annotation.DbUnitConfiguration;
 4import org.junit.Before;
 5import org.junit.experimental.categories.Category;
 6import org.junit.runner.RunWith;
 7import org.springframework.beans.factory.annotation.Autowired;
 8import org.springframework.test.context.ActiveProfiles;
 9import org.springframework.test.context.ContextConfiguration;
10import org.springframework.test.context.TestExecutionListeners;
11import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
12import org.springframework.test.context.support.DependencyInjectionTestExecutionListener;
13import org.springframework.test.context.transaction.TransactionalTestExecutionListener;
14import org.springframework.test.context.web.ServletTestExecutionListener;
15import org.springframework.test.context.web.WebAppConfiguration;
16import org.springframework.test.web.servlet.MockMvc;
17import org.springframework.test.web.servlet.ResultActions;
18import org.springframework.test.web.servlet.setup.MockMvcBuilders;
19import org.springframework.web.context.WebApplicationContext;
20
21import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
22
23@RunWith(SpringJUnit4ClassRunner.class)
24@ContextConfiguration(classes = {IntegrationTestContext.class})
25@WebAppConfiguration
26@TestExecutionListeners({
27        DependencyInjectionTestExecutionListener.class,
28        TransactionalTestExecutionListener.class,
29        DbUnitTestExecutionListener.class,
30        ServletTestExecutionListener.class
31})
32@DatabaseSetup("/com/testwithspring/intermediate/tasks.xml")
33@DbUnitConfiguration(dataSetLoader = ReplacementDataSetLoader.class)
34@Category(IntegrationTest.class)
35@ActiveProfiles(Profiles.INTEGRATION_TEST)
36public class SearchTasksWhenOneTaskIsFound {
37
38    @Autowired
39    private WebApplicationContext webAppContext;
40
41    private MockMvc mockMvc;
42
43    @Before
44    public void configureSystemUnderTest() {
45        mockMvc = MockMvcBuilders.webAppContextSetup(webAppContext)
46                .build();
47    }
48
49    private ResultActions search() throws Exception {
50        return mockMvc.perform(get("/api/task/search")
51                .param("searchTerm", Tasks.SEARCH_TERM_ONE_MATCH)
52        );
53    }
54}

After we have configured our test cases, we have to write four integration tests for the tested controller method.

First, we have to ensure that it returns the HTTP status code 200. After we have written this test method, the source code of our test class looks as follows:

 1import com.github.springtestdbunit.DbUnitTestExecutionListener;
 2import com.github.springtestdbunit.annotation.DatabaseSetup;
 3import com.github.springtestdbunit.annotation.DbUnitConfiguration;
 4import org.junit.Before;
 5import org.junit.Test;
 6import org.junit.experimental.categories.Category;
 7import org.junit.runner.RunWith;
 8import org.springframework.beans.factory.annotation.Autowired;
 9import org.springframework.test.context.ActiveProfiles;
10import org.springframework.test.context.ContextConfiguration;
11import org.springframework.test.context.TestExecutionListeners;
12import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
13import org.springframework.test.context.support.DependencyInjectionTestExecutionListener;
14import org.springframework.test.context.transaction.TransactionalTestExecutionListener;
15import org.springframework.test.context.web.ServletTestExecutionListener;
16import org.springframework.test.context.web.WebAppConfiguration;
17import org.springframework.test.web.servlet.MockMvc;
18import org.springframework.test.web.servlet.ResultActions;
19import org.springframework.test.web.servlet.setup.MockMvcBuilders;
20import org.springframework.web.context.WebApplicationContext;
21
22import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
23import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
24
25@RunWith(SpringJUnit4ClassRunner.class)
26@ContextConfiguration(classes = {IntegrationTestContext.class})
27@WebAppConfiguration
28@TestExecutionListeners({
29        DependencyInjectionTestExecutionListener.class,
30        TransactionalTestExecutionListener.class,
31        DbUnitTestExecutionListener.class,
32        ServletTestExecutionListener.class
33})
34@DatabaseSetup("/com/testwithspring/intermediate/tasks.xml")
35@DbUnitConfiguration(dataSetLoader = ReplacementDataSetLoader.class)
36@Category(IntegrationTest.class)
37@ActiveProfiles(Profiles.INTEGRATION_TEST)
38public class SearchTasksWhenOneTaskIsFound {
39
40    @Autowired
41    private WebApplicationContext webAppContext;
42
43    private MockMvc mockMvc;
44
45    @Before
46    public void configureSystemUnderTest() {
47        mockMvc = MockMvcBuilders.webAppContextSetup(webAppContext)
48                .build();
49    }
50
51    @Test
52    public void shouldReturnHttpStatusCodeOk() throws Exception {
53        search()
54                .andExpect(status().isOk());
55    }
56
57    private ResultActions search() throws Exception {
58        return mockMvc.perform(get("/api/task/search")
59                .param("searchTerm", Tasks.SEARCH_TERM_ONE_MATCH)
60        );
61    }
62}

Second, we have to verify that it returns the found task as JSON. After we have written this integration test, the relevant part of our test class looks as follows:

 1import com.github.springtestdbunit.DbUnitTestExecutionListener;
 2import com.github.springtestdbunit.annotation.DatabaseSetup;
 3import com.github.springtestdbunit.annotation.DbUnitConfiguration;
 4import org.junit.Before;
 5import org.junit.Test;
 6import org.junit.experimental.categories.Category;
 7import org.junit.runner.RunWith;
 8import org.springframework.beans.factory.annotation.Autowired;
 9import org.springframework.http.MediaType;
10import org.springframework.test.context.ActiveProfiles;
11import org.springframework.test.context.ContextConfiguration;
12import org.springframework.test.context.TestExecutionListeners;
13import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
14import org.springframework.test.context.support.DependencyInjectionTestExecutionListener;
15import org.springframework.test.context.transaction.TransactionalTestExecutionListener;
16import org.springframework.test.context.web.ServletTestExecutionListener;
17import org.springframework.test.context.web.WebAppConfiguration;
18import org.springframework.test.web.servlet.MockMvc;
19import org.springframework.test.web.servlet.ResultActions;
20import org.springframework.test.web.servlet.setup.MockMvcBuilders;
21import org.springframework.web.context.WebApplicationContext;
22
23import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
24import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
25
26@RunWith(SpringJUnit4ClassRunner.class)
27@ContextConfiguration(classes = {IntegrationTestContext.class})
28@WebAppConfiguration
29@TestExecutionListeners({
30        DependencyInjectionTestExecutionListener.class,
31        TransactionalTestExecutionListener.class,
32        DbUnitTestExecutionListener.class,
33        ServletTestExecutionListener.class
34})
35@DatabaseSetup("/com/testwithspring/intermediate/tasks.xml")
36@DbUnitConfiguration(dataSetLoader = ReplacementDataSetLoader.class)
37@Category(IntegrationTest.class)
38@ActiveProfiles(Profiles.INTEGRATION_TEST)
39public class SearchTasksWhenOneTaskIsFound {
40
41    @Autowired
42    private WebApplicationContext webAppContext;
43
44    private MockMvc mockMvc;
45
46    @Before
47    public void configureSystemUnderTest() {
48        mockMvc = MockMvcBuilders.webAppContextSetup(webAppContext)
49                .build();
50    }
51
52    @Test
53    public void shouldReturnSearchResultsAsJson() throws Exception {
54        search()
55                .andExpect(
56                        content().contentType(MediaType.APPLICATION_JSON_UTF8)
57                );
58    }
59
60    private ResultActions search() throws Exception {
61        return mockMvc.perform(get("/api/task/search")
62                .param("searchTerm", Tasks.SEARCH_TERM_ONE_MATCH)
63        );
64    }
65}

Third, we have to ensure that it returns one task. After we have implemented this test method, the relevant part of our test class looks as follows:

 1import com.github.springtestdbunit.DbUnitTestExecutionListener;
 2import com.github.springtestdbunit.annotation.DatabaseSetup;
 3import com.github.springtestdbunit.annotation.DbUnitConfiguration;
 4import org.junit.Before;
 5import org.junit.Test;
 6import org.junit.experimental.categories.Category;
 7import org.junit.runner.RunWith;
 8import org.springframework.beans.factory.annotation.Autowired;
 9import org.springframework.test.context.ActiveProfiles;
10import org.springframework.test.context.ContextConfiguration;
11import org.springframework.test.context.TestExecutionListeners;
12import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
13import org.springframework.test.context.support.DependencyInjectionTestExecutionListener;
14import org.springframework.test.context.transaction.TransactionalTestExecutionListener;
15import org.springframework.test.context.web.ServletTestExecutionListener;
16import org.springframework.test.context.web.WebAppConfiguration;
17import org.springframework.test.web.servlet.MockMvc;
18import org.springframework.test.web.servlet.ResultActions;
19import org.springframework.test.web.servlet.setup.MockMvcBuilders;
20import org.springframework.web.context.WebApplicationContext;
21
22import static org.hamcrest.Matchers.hasSize;
23import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
24import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
25
26@RunWith(SpringJUnit4ClassRunner.class)
27@ContextConfiguration(classes = {IntegrationTestContext.class})
28@WebAppConfiguration
29@TestExecutionListeners({
30        DependencyInjectionTestExecutionListener.class,
31        TransactionalTestExecutionListener.class,
32        DbUnitTestExecutionListener.class,
33        ServletTestExecutionListener.class
34})
35@DatabaseSetup("/com/testwithspring/intermediate/tasks.xml")
36@DbUnitConfiguration(dataSetLoader = ReplacementDataSetLoader.class)
37@Category(IntegrationTest.class)
38@ActiveProfiles(Profiles.INTEGRATION_TEST)
39public class SearchTasksWhenOneTaskIsFound {
40
41    @Autowired
42    private WebApplicationContext webAppContext;
43
44    private MockMvc mockMvc;
45
46    @Before
47    public void configureSystemUnderTest() {
48        mockMvc = MockMvcBuilders.webAppContextSetup(webAppContext)
49                .build();
50    }
51
52    @Test
53    public void shouldReturnOneTask() throws Exception {
54        search()
55                .andExpect(jsonPath("$", hasSize(1)));
56    }
57
58    private ResultActions search() throws Exception {
59        return mockMvc.perform(get("/api/task/search")
60                .param("searchTerm", Tasks.SEARCH_TERM_ONE_MATCH)
61        );
62    }
63}

Fourth, we have to verify that it returns the information of the correct task. After we have written this integration test, the relevant part of our test class looks as follows:

 1import com.github.springtestdbunit.DbUnitTestExecutionListener;
 2import com.github.springtestdbunit.annotation.DatabaseSetup;
 3import com.github.springtestdbunit.annotation.DbUnitConfiguration;
 4import org.junit.Before;
 5import org.junit.Test;
 6import org.junit.experimental.categories.Category;
 7import org.junit.runner.RunWith;
 8import org.springframework.beans.factory.annotation.Autowired;
 9import org.springframework.test.context.ActiveProfiles;
10import org.springframework.test.context.ContextConfiguration;
11import org.springframework.test.context.TestExecutionListeners;
12import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
13import org.springframework.test.context.support.DependencyInjectionTestExecutionListener;
14import org.springframework.test.context.transaction.TransactionalTestExecutionListener;
15import org.springframework.test.context.web.ServletTestExecutionListener;
16import org.springframework.test.context.web.WebAppConfiguration;
17import org.springframework.test.web.servlet.MockMvc;
18import org.springframework.test.web.servlet.ResultActions;
19import org.springframework.test.web.servlet.setup.MockMvcBuilders;
20import org.springframework.web.context.WebApplicationContext;
21
22import static org.hamcrest.Matchers.is;
23import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
24import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
25
26@RunWith(SpringJUnit4ClassRunner.class)
27@ContextConfiguration(classes = {IntegrationTestContext.class})
28@WebAppConfiguration
29@TestExecutionListeners({
30        DependencyInjectionTestExecutionListener.class,
31        TransactionalTestExecutionListener.class,
32        DbUnitTestExecutionListener.class,
33        ServletTestExecutionListener.class
34})
35@DatabaseSetup("/com/testwithspring/intermediate/tasks.xml")
36@DbUnitConfiguration(dataSetLoader = ReplacementDataSetLoader.class)
37@Category(IntegrationTest.class)
38@ActiveProfiles(Profiles.INTEGRATION_TEST)
39public class SearchTasksWhenOneTaskIsFound {
40
41    @Autowired
42    private WebApplicationContext webAppContext;
43
44    private MockMvc mockMvc;
45
46    @Before
47    public void configureSystemUnderTest() {
48        mockMvc = MockMvcBuilders.webAppContextSetup(webAppContext)
49                .build();
50    }
51
52    @Test
53    public void shouldReturnInformationOfFoundTask() throws Exception {
54        search()
55                .andExpect(jsonPath("$[0].id",
56                        is(Tasks.WriteLesson.ID.intValue())
57                ))
58                .andExpect(jsonPath("$[0].status",
59                        is(Tasks.WriteLesson.STATUS.toString())
60                ))
61                .andExpect(jsonPath("$[0].title",
62                        is(Tasks.WriteLesson.TITLE)
63                ));
64    }
65
66    private ResultActions search() throws Exception {
67        return mockMvc.perform(get("/api/task/search")
68                .param("searchTerm", Tasks.SEARCH_TERM_ONE_MATCH)
69        );
70    }
71}

We have now written integration tests for two different controller methods that return information found from the database. Let’s summarize what we learned from this lesson.

Summary

This lesson has taught us three things:

  • If we are writing integration tests for finder methods, typically all test methods can use the same data set.
  • We can support multiple time zones by making sure that the created test data uses the system’s default time zone.
  • We can reduce duplicate code by writing helper methods that create HTTP requests and send the created requests to the tested controller methods.

Get the source code from Github