Writing Integration Tests With JUnit 5
After we have finished this webinar, we
- Can get the required dependencies with Maven.
- Know how we can configure integration tests which use the Spring MVC Test framework and JUnit 5.
- Can write “normal” integration tests for Spring web applications.
- Understand how we can write nested integration tests for Spring web applications.
This webinar assumes that:
Getting the Required Dependencies
Before we can write integration tests for our Spring web application, we have to get the dependency that contains the Spring TestContext Framework extension for JUnit 5.
If we are using Spring 5, we have to declare the spring-test dependency in our build script.
On the other hand, if we are using Spring 4, we have to declare the Spring TestContext Framework Extension dependency in our POM file. This dependency is a prototype that allows us to integrate the Spring TestContext Framework with JUnit 5.
We can get the required dependencies by following these steps:
First, we have to ensure that Maven uses the Jitpack Maven repository when it resolves the dependencies of our Maven project. We can do this by adding the following snippet to our pom.xml file:
1<repositories>
2 <repository>
3 <id>jitpack.io</id>
4 <url>https://jitpack.io</url>
5 </repository>
6</repositories>
Second, we have to declare the Spring TestContext Framework Extension dependency in our POM file. We can do this by adding the following snippet to the dependencies section of our POM file:
1<dependency>
2 <groupId>com.github.sbrannen</groupId>
3 <artifactId>spring-test-junit5</artifactId>
4 <version>1.0.0</version>
5 <scope>test</scope>
6</dependency>
There are two things I want to point out:
- The example application of this webinar doesn’t support Gradle because I wasn’t able to create a Gradle project that can run integration tests which are not found from the src/test/java directory.
- The Spring TestContext Framework Extension is a prototype and it’s not maintained anymore. That’s why you should use Spring 5 if you want to write integration tests for Spring web applications with JUnit 5.
Next, we will take a quick look at the system under test.
Introduction to System Under Test
During this webinar we will write integration tests for a controller method that processes GET requests send to the path: ‘/api/message’. This method finds all messages from the database and returns the found messages as JSON. The source code of the system under test 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
6import java.util.List;
7
8@RestController
9@RequestMapping("/api/message")
10class MessageController {
11
12 private final MessageService service;
13
14 @Autowired
15 MessageController(MessageService service) {
16 this.service = service;
17 }
18
19 @RequestMapping(method = RequestMethod.GET)
20 List<MessageDTO> findAll() {
21 return service.findAll();
22 }
23}
If one message is found from the database, the returned JSON document could look as follows:
1[
2 {
3 "id": 1,
4 "text": "Hello World!"
5 }
6]
Let’s move on and find out how we can configure our integration tests.
Configuring Our Integration Tests
We can configure our integration tests by following these steps:
- Register a new extension called
SpringExtension. This extension integrates the Spring TestContext framework with JUnit 5. - Configure the application context configuration class that configures the loaded application context. Because we are writing integration tests for a Spring MVC controller, we have to ensure that the loaded application context is an instance of
WebApplicationContext. - Register a test execution listener called
DbUnitTestExecutionListenerthat integrates DbUnit with the Spring Test framework. - Inject the loaded
WebApplicationContextinto our test class. - Add a
MockMvcfield to our test class. - Add a setup method to our test class and ensure that this method is run before each test method.
- Implement the setup method by creating a new
MockMvcobject. Because we are writing integration tests, we have to use the web application context based configuration.
After we have configured our integration tests, the source code of our test class looks as follows:
1import com.github.springtestdbunit.DbUnitTestExecutionListener;
2import com.github.springtestdbunit.annotation.DatabaseSetup;
3import org.junit.jupiter.api.BeforeEach;
4import org.junit.jupiter.api.DisplayName;
5import org.junit.jupiter.api.extension.ExtendWith;
6import org.springframework.beans.factory.annotation.Autowired;
7import org.springframework.test.context.ContextConfiguration;
8import org.springframework.test.context.TestExecutionListeners;
9import org.springframework.test.context.junit.jupiter.SpringExtension;
10import org.springframework.test.context.web.WebAppConfiguration;
11import org.springframework.test.web.servlet.MockMvc;
12import org.springframework.test.web.servlet.setup.MockMvcBuilders;
13import org.springframework.web.context.WebApplicationContext;
14
15import static org.springframework.test.context.TestExecutionListeners.*;
16
17@ExtendWith(SpringExtension.class)
18@ContextConfiguration(classes = {ExampleApplicationContext.class})
19@WebAppConfiguration
20@TestExecutionListeners(value = { DbUnitTestExecutionListener.class },
21 mergeMode = MergeMode.MERGE_WITH_DEFAULTS
22)
23@DisplayName("Find all messages")
24class FindAllMessagesIT {
25
26 @Autowired
27 private WebApplicationContext webApplicationContext;
28
29 private MockMvc mockMvc;
30
31 @BeforeEach
32 void configureSystemUnderTest() {
33 mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext)
34 .build();
35 }
36}
This configuration uses the “traditional” annotations which are used to configure the loaded application context. Even though this works, we can make our configuration a bit shorter by following these steps:
- Remove the
@ExtendWith,@ContextConfiguration, and@WebAppConfigurationannotations. - Annotate our test class with the
@SpringJUnitJupiterWebConfigannotation and configure the used application context configuration class.
After we have replaced the traditional annotations, the source code of our test class looks as follows:
1import com.github.springtestdbunit.DbUnitTestExecutionListener;
2import org.junit.jupiter.api.BeforeEach;
3import org.junit.jupiter.api.DisplayName;
4import org.springframework.beans.factory.annotation.Autowired;
5import org.springframework.test.context.TestExecutionListeners;
6import org.springframework.test.context.junit.jupiter.web.SpringJUnitJupiterWebConfig;
7import org.springframework.test.web.servlet.MockMvc;
8import org.springframework.test.web.servlet.setup.MockMvcBuilders;
9import org.springframework.web.context.WebApplicationContext;
10
11import static org.springframework.test.context.TestExecutionListeners.MergeMode;
12
13@SpringJUnitJupiterWebConfig(classes = {ExampleApplicationContext.class})
14@TestExecutionListeners(value = { DbUnitTestExecutionListener.class },
15 mergeMode = MergeMode.MERGE_WITH_DEFAULTS
16)
17@DisplayName("Find all messages")
18class FindAllMessagesIT {
19
20 @Autowired
21 private WebApplicationContext webApplicationContext;
22
23 private MockMvc mockMvc;
24
25 @BeforeEach
26 void configureSystemUnderTest() {
27 mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext)
28 .build();
29 }
30}
SpringJUnitWebConfig annotation.
Next, we will find out how we can write “normal” integration tests for our Spring MVC controller.
Writing “Normal” Integration Tests
We can write normal integration tests for the tested controller method by following these steps:
First, we have to configure the DbUnit data set that inserts the required test data into our database. After we have done this, the source code of our test class looks as follows:
1import com.github.springtestdbunit.DbUnitTestExecutionListener;
2import com.github.springtestdbunit.annotation.DatabaseSetup;
3import org.junit.jupiter.api.BeforeEach;
4import org.junit.jupiter.api.DisplayName;
5import org.springframework.beans.factory.annotation.Autowired;
6import org.springframework.test.context.TestExecutionListeners;
7import org.springframework.test.context.junit.jupiter.web.SpringJUnitJupiterWebConfig;
8import org.springframework.test.web.servlet.MockMvc;
9import org.springframework.test.web.servlet.setup.MockMvcBuilders;
10import org.springframework.web.context.WebApplicationContext;
11
12import static org.springframework.test.context.TestExecutionListeners.MergeMode;
13
14@SpringJUnitJupiterWebConfig(classes = {ExampleApplicationContext.class})
15@TestExecutionListeners(value = { DbUnitTestExecutionListener.class },
16 mergeMode = MergeMode.MERGE_WITH_DEFAULTS
17)
18@DatabaseSetup("/com/testwithspring/intermediate/messages.xml")
19@DisplayName("Find all messages")
20class FindAllMessagesIT {
21
22 @Autowired
23 private WebApplicationContext webApplicationContext;
24
25 private MockMvc mockMvc;
26
27 @BeforeEach
28 void configureSystemUnderTest() {
29 mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext)
30 .build();
31 }
32}
The DbUnit data set that inserts the required test data into our database (messages.xml) looks as follows:
1<dataset>
2 <messages id="1" text="Hello World!"/>
3</dataset>
Second, we have to write the actual integration tests by following these steps:
- Write a utility method that sends a
GETrequest to the path: ‘/api/message’ and returns aResultActionsobject. - Ensure that the tested controller method returns the HTTP status code 200.
- Verify that the tested controller method returns the found messages as JSON.
- Ensure that the tested controller method returns one message.
- Verify that the tested controller method returns the found message.
After we have written the required integration tests, the source code of our test class looks as follows:
1import com.github.springtestdbunit.DbUnitTestExecutionListener;
2import com.github.springtestdbunit.annotation.DatabaseSetup;
3import org.junit.jupiter.api.BeforeEach;
4import org.junit.jupiter.api.DisplayName;
5import org.junit.jupiter.api.Test;
6import org.springframework.beans.factory.annotation.Autowired;
7import org.springframework.http.MediaType;
8import org.springframework.test.context.TestExecutionListeners;
9import org.springframework.test.context.junit.jupiter.web.SpringJUnitJupiterWebConfig;
10import org.springframework.test.web.servlet.MockMvc;
11import org.springframework.test.web.servlet.ResultActions;
12import org.springframework.test.web.servlet.setup.MockMvcBuilders;
13import org.springframework.web.context.WebApplicationContext;
14
15import static org.hamcrest.Matchers.hasSize;
16import static org.hamcrest.Matchers.is;
17import static org.springframework.test.context.TestExecutionListeners.MergeMode;
18import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
19import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
20
21@SpringJUnitJupiterWebConfig(classes = {ExampleApplicationContext.class})
22@TestExecutionListeners(value = { DbUnitTestExecutionListener.class },
23 mergeMode = MergeMode.MERGE_WITH_DEFAULTS
24)
25@DatabaseSetup("/com/testwithspring/intermediate/messages.xml")
26@DisplayName("Find all messages")
27class FindAllMessagesIT {
28
29 @Autowired
30 private WebApplicationContext webApplicationContext;
31
32 private MockMvc mockMvc;
33
34 @BeforeEach
35 void configureSystemUnderTest() {
36 mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext)
37 .build();
38 }
39
40 @Test
41 @DisplayName("Should return the HTTP status code OK")
42 void shouldReturnHttpStatusCodeOk() throws Exception {
43 findAllMessages()
44 .andExpect(status().isOk());
45 }
46
47 @Test
48 @DisplayName("Should return the found messages as JSON")
49 void shouldReturnFoundMessagesAsJson() throws Exception {
50 findAllMessages()
51 .andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8));
52 }
53
54 @Test
55 @DisplayName("Should return a list that has one message")
56 void shouldReturnEmptyList() throws Exception {
57 findAllMessages()
58 .andExpect(jsonPath("$", hasSize(1)));
59 }
60
61 @Test
62 @DisplayName("Should return the information of the found message")
63 void shouldReturnInformationOfFoundMessage() throws Exception {
64 findAllMessages()
65 .andExpect(jsonPath("$[0].id", is(1)))
66 .andExpect(jsonPath("$[0].text", is("Hello World!")));
67 }
68
69 private ResultActions findAllMessages() throws Exception {
70 return mockMvc.perform(get("/api/message"));
71 }
72}
As we can see, these integration tests help us to verify that the system under test is working as expected when one message is found. However, the odds are that we also want to ensure that the system under test is working correctly when no messages is found. Let’s find out how we can do this by writing nested integration tests.
Writing Nested Integration Tests
We can write nested integration tests for the system under test by making the following changes to our test class:
First, we have to remove the @DatabaseSetup annotation and delete the test methods which write assertions for the JSON document returned by the tested controller method.
After we have done this, the source code of our test class looks as follows:
1import com.github.springtestdbunit.DbUnitTestExecutionListener;
2import org.junit.jupiter.api.BeforeEach;
3import org.junit.jupiter.api.DisplayName;
4import org.junit.jupiter.api.Test;
5import org.junit.jupiter.api.extension.ExtendWith;
6import org.springframework.beans.factory.annotation.Autowired;
7import org.springframework.http.MediaType;
8import org.springframework.test.context.ContextConfiguration;
9import org.springframework.test.context.TestExecutionListeners;
10import org.springframework.test.context.junit.jupiter.SpringExtension;
11import org.springframework.test.context.web.WebAppConfiguration;
12import org.springframework.test.web.servlet.MockMvc;
13import org.springframework.test.web.servlet.ResultActions;
14import org.springframework.test.web.servlet.setup.MockMvcBuilders;
15import org.springframework.web.context.WebApplicationContext;
16
17import static org.springframework.test.context.TestExecutionListeners.MergeMode;
18import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
19import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
20
21@ExtendWith(SpringExtension.class)
22@ContextConfiguration(classes = {ExampleApplicationContext.class})
23@WebAppConfiguration
24@TestExecutionListeners(value = { DbUnitTestExecutionListener.class },
25 mergeMode = MergeMode.MERGE_WITH_DEFAULTS
26)
27@DisplayName("Find all messages")
28class FindAllMessagesIT {
29
30 @Autowired
31 private WebApplicationContext webApplicationContext;
32
33 private MockMvc mockMvc;
34
35 @BeforeEach
36 void configureSystemUnderTest() {
37 mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext)
38 .build();
39 }
40
41 @Test
42 @DisplayName("Should return the HTTP status code OK")
43 void shouldReturnHttpStatusCodeOk() throws Exception {
44 findAllMessages()
45 .andExpect(status().isOk());
46 }
47
48 @Test
49 @DisplayName("Should return the found messages as JSON")
50 void shouldReturnFoundMessagesAsJson() throws Exception {
51 findAllMessages()
52 .andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8));
53 }
54
55 private ResultActions findAllMessages() throws Exception {
56 return mockMvc.perform(get("/api/message"));
57 }
58}
Second, we have to write an integration test which ensures that the system under test is working as expected when no messages is found from the database. We can write this integration test by following these steps:
- Add a new nested test class to the
FindAllMessagesITclass. - Configure the loaded application context configuration class and register the required test execution listener. By the way, we have to do this only because the Spring TestContext Framework extension doesn’t have a proper support for nested integration tests. This support will be released in the future.
- Configure the DbUnit data set which ensures that no messages is found from the database.
- Verify that the tested controller method returns an empty list.
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 org.junit.jupiter.api.BeforeEach;
4import org.junit.jupiter.api.DisplayName;
5import org.junit.jupiter.api.Nested;
6import org.junit.jupiter.api.Test;
7import org.junit.jupiter.api.extension.ExtendWith;
8import org.springframework.beans.factory.annotation.Autowired;
9import org.springframework.http.MediaType;
10import org.springframework.test.context.ContextConfiguration;
11import org.springframework.test.context.TestExecutionListeners;
12import org.springframework.test.context.junit.jupiter.SpringExtension;
13import org.springframework.test.context.web.WebAppConfiguration;
14import org.springframework.test.web.servlet.MockMvc;
15import org.springframework.test.web.servlet.ResultActions;
16import org.springframework.test.web.servlet.setup.MockMvcBuilders;
17import org.springframework.web.context.WebApplicationContext;
18
19import static org.hamcrest.Matchers.hasSize;
20import static org.springframework.test.context.TestExecutionListeners.MergeMode;
21import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
22import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
23
24@ExtendWith(SpringExtension.class)
25@ContextConfiguration(classes = {ExampleApplicationContext.class})
26@WebAppConfiguration
27@TestExecutionListeners(value = { DbUnitTestExecutionListener.class },
28 mergeMode = MergeMode.MERGE_WITH_DEFAULTS
29)
30@DisplayName("Find all messages")
31class FindAllMessagesIT {
32
33 @Autowired
34 private WebApplicationContext webApplicationContext;
35
36 private MockMvc mockMvc;
37
38 @BeforeEach
39 void configureSystemUnderTest() {
40 mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext)
41 .build();
42 }
43
44 @Test
45 @DisplayName("Should return the HTTP status code OK")
46 void shouldReturnHttpStatusCodeOk() throws Exception {
47 findAllMessages()
48 .andExpect(status().isOk());
49 }
50
51 @Test
52 @DisplayName("Should return the found messages as JSON")
53 void shouldReturnFoundMessagesAsJson() throws Exception {
54 findAllMessages()
55 .andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8));
56 }
57
58 @Nested
59 @DisplayName("When no messages is found from the database")
60 @ContextConfiguration(classes = {ExampleApplicationContext.class})
61 @WebAppConfiguration
62 @TestExecutionListeners(value = { DbUnitTestExecutionListener.class },
63 mergeMode = MergeMode.MERGE_WITH_DEFAULTS
64 )
65 @DatabaseSetup("/com/testwithspring/intermediate/no-messages.xml")
66 class WhenNoMessagesIsFound {
67
68 @Test
69 @DisplayName("Should return an empty list")
70 void shouldReturnEmptyList() throws Exception {
71 findAllMessages()
72 .andExpect(jsonPath("$", hasSize(0)));
73 }
74 }
75
76 private ResultActions findAllMessages() throws Exception {
77 return mockMvc.perform(get("/api/message"));
78 }
79}
Additional Reading:
The DbUnit data set which ensures that no messages is found from the database (no-messages.xml) looks as follows:
1<dataset>
2 <messages/>
3</dataset>
Third, we have to write the integration tests which ensure that the system under test is working as expected when one message is found from the database. We can write these integration tests by following these steps:
- Add a new nested test class to the
FindAllMessagesITclass. - Configure the loaded application context configuration class and register the required test execution listener.
- Configure the DbUnit data set which inserts the required test data into our database.
- Verify that the tested controller method returns one message.
- Ensure that the tested controller method returns the information of the found message.
After we have written these integration tests, the source code of our test class looks as follows:
1import com.github.springtestdbunit.DbUnitTestExecutionListener;
2import com.github.springtestdbunit.annotation.DatabaseSetup;
3import org.junit.jupiter.api.BeforeEach;
4import org.junit.jupiter.api.DisplayName;
5import org.junit.jupiter.api.Nested;
6import org.junit.jupiter.api.Test;
7import org.junit.jupiter.api.extension.ExtendWith;
8import org.springframework.beans.factory.annotation.Autowired;
9import org.springframework.http.MediaType;
10import org.springframework.test.context.ContextConfiguration;
11import org.springframework.test.context.TestExecutionListeners;
12import org.springframework.test.context.junit.jupiter.SpringExtension;
13import org.springframework.test.context.web.WebAppConfiguration;
14import org.springframework.test.web.servlet.MockMvc;
15import org.springframework.test.web.servlet.ResultActions;
16import org.springframework.test.web.servlet.setup.MockMvcBuilders;
17import org.springframework.web.context.WebApplicationContext;
18
19import static org.hamcrest.Matchers.hasSize;
20import static org.hamcrest.Matchers.is;
21import static org.springframework.test.context.TestExecutionListeners.MergeMode;
22import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
23import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
24
25@ExtendWith(SpringExtension.class)
26@ContextConfiguration(classes = {ExampleApplicationContext.class})
27@WebAppConfiguration
28@TestExecutionListeners(value = { DbUnitTestExecutionListener.class },
29 mergeMode = MergeMode.MERGE_WITH_DEFAULTS
30)
31@DisplayName("Find all messages")
32class FindAllMessagesIT {
33
34 @Autowired
35 private WebApplicationContext webApplicationContext;
36
37 private MockMvc mockMvc;
38
39 @BeforeEach
40 void configureSystemUnderTest() {
41 mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext)
42 .build();
43 }
44
45 @Test
46 @DisplayName("Should return the HTTP status code OK")
47 void shouldReturnHttpStatusCodeOk() throws Exception {
48 findAllMessages()
49 .andExpect(status().isOk());
50 }
51
52 @Test
53 @DisplayName("Should return the found messages as JSON")
54 void shouldReturnFoundMessagesAsJson() throws Exception {
55 findAllMessages()
56 .andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8));
57 }
58
59 @Nested
60 @DisplayName("When no messages is found from the database")
61 @ContextConfiguration(classes = {ExampleApplicationContext.class})
62 @WebAppConfiguration
63 @TestExecutionListeners(value = { DbUnitTestExecutionListener.class },
64 mergeMode = MergeMode.MERGE_WITH_DEFAULTS
65 )
66 @DatabaseSetup("/com/testwithspring/intermediate/no-messages.xml")
67 class WhenNoMessagesIsFound {
68
69 @Test
70 @DisplayName("Should return an empty list")
71 void shouldReturnEmptyList() throws Exception {
72 findAllMessages()
73 .andExpect(jsonPath("$", hasSize(0)));
74 }
75 }
76
77 @Nested
78 @DisplayName("When one message is found from the database")
79 @ContextConfiguration(classes = {ExampleApplicationContext.class})
80 @WebAppConfiguration
81 @TestExecutionListeners(value = { DbUnitTestExecutionListener.class },
82 mergeMode = MergeMode.MERGE_WITH_DEFAULTS
83 )
84 @DatabaseSetup("/com/testwithspring/intermediate/messages.xml")
85 class WhenOneMessageIsFound {
86
87 @Test
88 @DisplayName("Should return a list that has one message")
89 void shouldReturnEmptyList() throws Exception {
90 findAllMessages()
91 .andExpect(jsonPath("$", hasSize(1)));
92 }
93
94 @Test
95 @DisplayName("Should return the information of the found message")
96 void shouldReturnInformationOfFoundMessage() throws Exception {
97 findAllMessages()
98 .andExpect(jsonPath("$[0].id", is(1)))
99 .andExpect(jsonPath("$[0].text", is("Hello World!")));
100 }
101 }
102
103 private ResultActions findAllMessages() throws Exception {
104 return mockMvc.perform(get("/api/message"));
105 }
106}
We have now written nested integration tests for the system under test. However, our nested test classes have duplicate annotations. We can remove these annotations by following these steps:
First, we have to create a meta annotation that allows us to create nested test classes and configure the loaded application context. The source code of the NestedSpringWebConfig annotation looks as follows:
1import org.junit.jupiter.api.Nested;
2import org.springframework.core.annotation.AliasFor;
3import org.springframework.test.context.ContextConfiguration;
4import org.springframework.test.context.web.WebAppConfiguration;
5
6import java.lang.annotation.*;
7
8@Nested
9@ContextConfiguration
10@WebAppConfiguration
11@Documented
12@Inherited
13@Retention(RetentionPolicy.RUNTIME)
14@Target({ElementType.TYPE})
15public @interface NestedSpringWebConfig {
16
17 @AliasFor(
18 annotation = ContextConfiguration.class
19 )
20 Class<?>[] classes() default {};
21}
Second, we have to replace the duplicate annotations with our new meta annotation and configure the application context configuration class which configures the loaded application context. After we have done this, the source code of our test class looks as follows:
1import com.github.springtestdbunit.DbUnitTestExecutionListener;
2import com.github.springtestdbunit.annotation.DatabaseSetup;
3import com.testwithspring.intermediate.config.ExampleApplicationContext;
4import org.junit.jupiter.api.BeforeEach;
5import org.junit.jupiter.api.DisplayName;
6import org.springframework.beans.factory.annotation.Autowired;
7import org.springframework.test.context.TestExecutionListeners;
8import org.springframework.test.context.junit.jupiter.web.SpringJUnitJupiterWebConfig;
9import org.springframework.test.web.servlet.MockMvc;
10import org.springframework.test.web.servlet.ResultActions;
11import org.springframework.test.web.servlet.setup.MockMvcBuilders;
12import org.springframework.web.context.WebApplicationContext;
13
14import static org.springframework.test.context.TestExecutionListeners.MergeMode;
15import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
16
17@SpringJUnitJupiterWebConfig(classes = {ExampleApplicationContext.class})
18@TestExecutionListeners(value = { DbUnitTestExecutionListener.class },
19 mergeMode = MergeMode.MERGE_WITH_DEFAULTS
20)
21@DisplayName("Find all messages")
22class FindAllMessagesIT {
23
24 @Autowired
25 private WebApplicationContext webApplicationContext;
26
27 private MockMvc mockMvc;
28
29 @BeforeEach
30 void configureSystemUnderTest() {
31 mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext)
32 .build();
33 }
34
35 //Tests are omitted for the sake of clarity
36
37 @DisplayName("When no messages is found from the database")
38 @NestedSpringWebConfig(classes = {ExampleApplicationContext.class})
39 @TestExecutionListeners(value = { DbUnitTestExecutionListener.class },
40 mergeMode = MergeMode.MERGE_WITH_DEFAULTS
41 )
42 @DatabaseSetup("/com/testwithspring/intermediate/no-messages.xml")
43 class WhenNoMessagesIsFound {
44
45 //Tests are omitted for the sake of clarity
46 }
47
48 @DisplayName("When one message is found from the database")
49 @NestedSpringWebConfig(classes = {ExampleApplicationContext.class})
50 @TestExecutionListeners(value = { DbUnitTestExecutionListener.class },
51 mergeMode = MergeMode.MERGE_WITH_DEFAULTS
52 )
53 @DatabaseSetup("/com/testwithspring/intermediate/messages.xml")
54 class WhenOneMessageIsFound {
55
56 //Tests are omitted for the sake of clarity
57 }
58
59 private ResultActions findAllMessages() throws Exception {
60 return mockMvc.perform(get("/api/message"));
61 }
62}
We can now write integration tests for Spring MVC controllers by using JUnit 5. Let’s summarize what we learned from this webinar.
Summary
This webinar has taught us six things:
- If we are using Spring 5, we have to declare the
spring-testdependency in our POM file. - If we are using Spring 4, we have to declare the Spring TestContext Framework Extension dependency in our POM file.
- The
SpringExtensionclass integrates the Spring TestContext framework with JUnit 5. - We can register the required extension and configure the loaded application context by using either the
@SpringJUnitJupiterWebConfigor@SpringJUnitWebConfigannotation. - If we want to write nested integration tests, we have to add the annotations which configure the loaded application context to every nested test class.
- It’s a good idea to create a meta annotation that helps us to eliminate duplicate annotations from nested test classes.