Integration Tests in Spring Boot with MockMvc, DBUnit and H2

In this article I show how to write integration tests in Spring Boot with MockMvc against controllers. Then, I show how to write tests in Spring Boot with DBUnit and H2 against a database.

Content:

  • The SpringBootTest annotation
  • The in-memory database H2

Check this video for more details.

All the code used in the article can be found in this repository.

The SpringBootTest annotation

The difference between testing services and testing controllers is that when testing a service I must call directly the method I want to test. But for the controllers I must call the URL with arguments, dynamic parameters, payloads or files.

This time Mockito is not enough, I must use the @SpringBootTest annotation. It prepares the web context and the dependency injection context. The MockMvc object belongs to the web context of a Spring test to perform HTTP requests. This time, I must inject the controller I want to test with the dependency injection context with the annotation @Autowired. And indicate which service will be injected as a mock with the annotation @MockBean. if i want to inject a spy I have the @SpyBean annotation.

@SpringBootTest
public class AuthenticationControllerTest {

    public MockMvc mockMvc;

    @Autowired
    public AuthenticationController authenticationController;

    @MockBean
    public UserService userService;

    private ObjectMapper objectMapper = new ObjectMapper();

    @BeforeEach
    public void setUp() {
        mockMvc = MockMvcBuilders.standaloneSetup(authenticationController).build();
    }

    @Test
    void testSignUp() throws Exception {
        // given
        SignUpDto signUpDto = SignUpDto.builder()
                .firstName("first")
                .lastName("last")
                .login("login")
                .password("pass".toCharArray())
                .build();

        when(userService.signUp(any()))
                .thenReturn(UserDto.builder().id(1L)
                        .firstName("first")
                        .lastName("last")
                        .token("token")
                        .build());

        // when then
        mockMvc.perform(post("/v1/signUp")
                .contentType(MediaType.APPLICATION_JSON_VALUE)
                .content(objectMapper.writeValueAsBytes(signUpDto))
        ).andExpect(status().is(201))
                .andExpect(jsonPath("$.token", is("token")));
    }
}

I will need the ObjectMapper to map an object to a JSON content. And to link the controller with the MockMvc object, I have the MockMvcBuilder.

To work with the Spring mocks, I must use the following syntax:

when(userService.signUp(any()))
                .thenReturn(UserDto.builder().id(1L)
                        .firstName("first")
                        .lastName("last")
                        .token("token")
                        .build());

This time, the validation, the assertions, can be made directly in the MockMvc output. I can validate it with the jsonPath method. With some kind of regex. Or getting the response, parsing it into an object, and verifying the fields of the object as done until now.

The in-memory database H2

Before running the test, as i need the Spring context to be loaded, I need all the components of my Spring application to be configured, as the database. For that, I will use an in-memory database H2.

        <dependency>
            <groupId>com.h2database</groupId>
            <artifactId>h2</artifactId>
            <scope>test</scope>
            <version>1.4.200</version>
        </dependency>
        <dependency>
            <groupId>org.dbunit</groupId>
            <artifactId>dbunit</artifactId>
            <version>2.7.0</version>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>junit</groupId>
                    <artifactId>junit</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>com.github.springtestdbunit</groupId>
            <artifactId>spring-test-dbunit</artifactId>
            <version>${spring-test-dbunit.version}</version>
        </dependency>

i need to exclude JUnit from dbunit to avoid conflicts with the version 5.

And now, configure the connection to this in-memory database in the application.yml of the test folder.

spring:
  profiles:
    active: test
  datasource:
    type: com.zaxxer.hikari.HikariDataSource
    url: jdbc:h2:mem:socialnetworkdb
    driverClassName: org.h2.Driver
    username: sa
    password:
    initialization-mode: always
  jpa:
    database-platform: org.hibernate.dialect.H2Dialect
    database: H2
    open-in-view: false

From now, executing the tests should take a lot more time because they have to load all the Spring context.

Let’s take a look about how to test Spring repositories against a database with some data. I’ve already configured an in-memory database H2. I did it because I don’t want to ensure an external database is created and the connection is correctly established. I want the database to be created on the fly, at each test session

I must add some XML files with the content that must be injected into the in-memory database.

<?xml version="1.0" encoding="UTF-8"?>
<dataset>
    <social_network_user
            id="10"
            first_name="first"
            last_name="last"
            login="login"
            password="password"
            created_date="2020-01-01 12:00:00"
    />
    <image
            id="100"
            title="title"
            path="/abd"
            user_id="10"
            created_date="2020-01-01 12:00:00"
    />
</dataset>

The XML file contains the structure of the tables that will be created on the fly. Each line define a single row. And each attribute of a line define the column names and their values.

To inject this XML file for a single unit test I will use the @DatabaseSetup annotation.

@SpringBootTest
@Transactional
@TestExecutionListeners({
        DependencyInjectionTestExecutionListener.class,
        DirtiesContextTestExecutionListener.class,
        TransactionalTestExecutionListener.class,
        DbUnitTestExecutionListener.class
})
public class UserRepositoryTest {

    @Autowired
    public UserRepository userRepository;

    @Test
    @DatabaseSetup("/dataset/users.xml")
    void testFindByLogin() {
        // given
        String login = "login";

        // when
        Optional<User> user = userRepository.findByLogin(login);

        // then
        assertAll(() -> {
            assertTrue(user.isPresent());
            assertEquals(login, user.get().getLogin());
            assertEquals(10L, user.get().getId());
        });
    }
}

The @Transactional annotation works in the reverse way for the test than for the production code. When present, the data won’t be persisted when the test ends. And I must add some listeners to indicate when the database must be cleaned and the dependency injection used.

The @DatabaseSetup annotation may be used at a class level if I want the dataset to be injected before running the tests of this class, or put it at the test level if I want the dataset to be injected only for the current test.

But now before running the test, i must ensure that Liquibase knows about the in-memory database. I will add an inclusion in the pom.xml.

<build>
        <pluginManagement>
            <plugins>
                <plugin>
                    <groupId>org.liquibase</groupId>
                    <artifactId>liquibase-maven-plugin</artifactId>
                    <version>3.10.0</version>
                    <configuration>
                        <propertyFile>${liquibase.propertyFile}</propertyFile>
                    </configuration>
                    <dependencies>
                        <strong><dependency>
                            <groupId>com.h2database</groupId>
                            <artifactId>h2</artifactId>
                            <version>1.4.200</version>
                        </dependency></strong>
                        <dependency>
                            <groupId>org.hibernate</groupId>
                            <artifactId>hibernate-core</artifactId>
                            <version>5.4.12.Final</version>
                        </dependency>
                        <dependency>
                            <groupId>javax.xml.bind</groupId>
                            <artifactId>jaxb-api</artifactId>
                            <version>2.3.1</version>
                        </dependency>
                    </dependencies>
                </plugin>
            </plugins>
        </pluginManagement>
    </build>

Conclusion

  • Annotate the tests with the SpringBootTest annotation to allow the Spring context being loaded;
  • Use the MockMvc object to inject the controller;
  • Make the assertions directly with the MockMvc result and jsonPath;
  • Configure the in-memory database H2;
  • Create XML files with the content to be injected in H2 during the tests;
  • Use the annotation DatabaseSetup to choose the XML file to be injected;
  • Tell Liquibase about the driver to use now to connect to the database.

References

Repository

My New ebook, How to Master Git With 20 Commands, is available now.

Leave a comment

A WordPress.com Website.