Adding Integration Tests in Spring Boot with Testcontainers
Tags: springboot, testing, testcontainers, java
Published: 2020-12-19 10:21:45

Good software always includes automated tests to detect errors during the creation and modification of the code. In this tutorial **integration tests will be added to our Spring Boot application**, connecting to the 'real' database using the Testcontainers library.

## Backgrounds
 
An integration test is meant to test the interaction of multiple parts of the application. So in our Spring Boot context it would be great to **examine the running app including the database** (for example MySQL or PostgreSQL). An embedded database would not be optimal for this, as there can be significant differences to the actual database, and therefore certain features may not work or errors may be missed.

For this purpose, there is the **Testcontainers library to start a Docker image of exactly the database** that is used by the developer and later in production.  To write an integration test for our application, we start by adding the required dependencies.

```gradle
testImplementation('org.springframework.boot:spring-boot-starter-test')
testImplementation('org.springframework.boot:spring-boot-testcontainers')
testImplementation('org.testcontainers:mysql')
```
_&nbsp; Adding dependencies to our build.gradle_

Besides `spring-boot-starter-test` we also add `org.springframework.boot:spring-boot-testcontainers`. Since Spring Boot `3.1.0` **there is first-class support for Testcontainers**, making our live even easier. With `org.testcontainers:mysql` we add the container class for the actual database we are using - in our case we choose MySQL.

## Starting the application context

To centralize the recurring logic of our test classes, **we first create an abstract base class** from which our IT classes will inherit later on. The first version of this class looks like this:

```java
@SpringBootTest(classes = MyAppApplication.class, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@AutoConfigureMockMvc
@ActiveProfiles("it")
public abstract class BaseIT {

    @Autowired
    public MockMvc mockMvc;

}
```
_&nbsp; First version of BaseIT.java_

By using the `@SpringBootTest` annotation, **Spring Boot will load the full application context** and thus enables us to make good integration tests. With `@ActiveProfiles` we define that the application is started with the profile `"it"`. If needed we can use this profile to activate special settings in our code.

By setting the variable `webEnvironment` to `RANDOM_PORT` the application will be started under a random and available port, so during testing there shouldn't be any conflict. `MockMvc` is configured automatically with the annotation `AutoConfigureMockMvc` - there are a lot of useful helpers included for calling our endpoints.

## Mocking the database

Now we want to configure a database that is exclusively available for our tests. For this we extend our `BaseIT.java` with the following code.

```java
public abstract class BaseIT {
    
    // ...

    @ServiceConnection
    private static final MySQLContainer mySQLContainer = new MySQLContainer("mysql:8.0");

    static {
        mySQLContainer.withUrlParam("serverTimezone", "UTC")
                .withReuse(true)
                .start();
    }

}
```
_&nbsp; Testcontainers setup to provide MySQL as a container_

The proper container from Testcontainers is initialized with the given image `"mysql:8.0"`, in our case the newest version of MySQL. Ideally this should **exactly match the version that is also used in production**.

Originally a special method annotated with `@DynamicPropertySource` was required to provide the connection credentials to the application context. With Spring Boot `3.1.0` that's not required anymore for most databases and simply adding `@ServiceConnection` will do exactly that.

Within the static block we are setting the reuse parameter, so that the container will continue running until we manually stop it. Usually that's not causing issues and makes testing the application faster - the Docker container can be stopped manually if required. To enable reuse, the file `/<usersdir>/.testcontainers.properties` in your environment must be extended by the entry `testcontainers.reuse.enable=true`.

Finally we are starting the container with a call to `start()`. This approach ensures that **all extending classes will reuse the same container**. Depending on how we initialize our database schema - for example with Flyway or Liquibase - this will be automatically applied to our test database within the container during context startup.

## Running the tests

With this preparation, we can write our first test class. Let's assume that the following `@RestController` already exists. The referenced service returns all entries that exist in the _"Test"_ table.

```java
@RestController
@RequestMapping(value = "/api/tests", produces = MediaType.APPLICATION_JSON_VALUE)
public class TestController {

    private final TestService testService;

    @Autowired
    public TestController(final TestService testService) {
        this.testService = testService;
    }

    @GetMapping
    public List<TestDTO> getAllTests() {
        return testService.findAll();
    }

}
```
_&nbsp; Example endpoint in our Spring Boot app_

We can now create a class `TestControllerIT` that extends our abstract base class. The test method sends a GET request to the existing endpoint using `MockMvc`. We're asserting that the HTTP status and the response JSON are matching our expectations.

```java
public class TestControllerIT extends BaseIT {

    @Test
    @Sql({"/data/clearAll.sql", "/data/testData.sql"})
    public void getAllTests_success() throws Exception {
        mockMvc.perform(get("/api/tests")
                        .accept(MediaType.APPLICATION_JSON))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.length()").value(1))
                .andExpect(jsonPath("$[0].id").value(((long)1000)));
    }

}
```
_&nbsp; TestControllerIT of our Spring Boot app_

Even though our database schema is already initialized, we still lack explicit test data that we need for our test. For this we use the Spring annotation `@Sql`, which executes two scripts and thus **puts our database into a known state**. In our case, there is now exactly one entry in the Test table that we expect as a result.

```sql
DELETE FROM test;
```
_&nbsp; Wiping out all data with src/test/resources/data/clearAll.sql_

```sql
INSERT INTO test (
    id,
    test
) VALUES (
    1000,
    'Aenean pulvinar...'
);
```
_&nbsp; Create a single table entry with testData.sql_

Docker must be available and running when the test is executed. When we run the test for the first time we have to wait a bit for our container to be available. It may be useful to already cache the image beforehand using `docker pull mysql:8.0` to avoid issues with the download. Our test should now go through without errors.

## Conclusion

With the setup described, we have **created a way to check the behavior of our application including the database** from a high level. With this we have a very useful addition to our unit tests. Testcontainers also offers support for other services such as RabbitMQ, so we can flexibly add such containers to BaseIT as needed.

**In the Professional plan, Bootify offers the option to activate Testcontainers.** This initializes the Spring Boot application including the described setup, depending on the selected database. It also generates the IT classes and scripts according to the tables and REST controllers created.

[**&raquo; See Features and Pricing**](https://bootify.io/pricing.html)
&nbsp;


### Further readings

[Testcontainers homepage](https://www.testcontainers.org/)  
[Official guide on testing Spring Boot applications](https://spring.io/guides/gs/testing-web/)  
[Docker Hub for finding images](https://hub.docker.com/)  
[@Sql and @SqlMergeMode explanation](https://docs.spring.io/spring-framework/docs/current/reference/html/testing.html#spring-testing-annotation-sql)  
[Where to look for the .testcontainers.properties](https://www.testcontainers.org/features/configuration/)