Write Spring Boot integration tests with Testcontainers (JUnit 4 & 5)
Tags: java, springboot, junit, testing
Published: 2020-07-02 17:19:15

Recently, I was looking for a solution to write integration tests for my Spring Boot-based application which was using PostgreSQL. I had the following requirements for this task:

1.  The integration tests should use the **same database** as in production (referring to the [Twelve-Factor App](https://12factor.net/) I wanted to keep my environment during the tests as similar as possible to the production environment)
2.  The tests should **not need any pre-setup** before running (e.g. like manually setting up a test database)
3.  The tests should use my **Flyway DDL** scripts and **create-drop** (`spring.jpa.hibernate.ddl-auto`) shouldn't be activated for my tests
4.  **Good integration** with the excellent Spring tests ecosystem

For this task, I found the awesome project: [Test containers](https://www.testcontainers.org/). The project describes itself as the following:

> "Testcontainers is a Java library that supports JUnit tests, providing lightweight, throwaway instances of common databases, Selenium web browsers, or anything else that can run in a Docker container."

With this blog post, we'll use Testcontainers to write integration tests with JUnit using a _real_ database (meaning not mocked or in-memory) for a [Spring Boot](https://github.com/rieckpil/blog-tutorials/tree/master/spring-boot-integration-tests-testcontainers) application. 

**UPDATE**: Time flies and a lot was introduced since I published this blog post. Therefore I added integration test examples for different combinations of JUnit 4 & 5 and Spring Boot versions.

## Setup Testcontainers in Spring Boot project

For using this dependency you need to have Docker on your local machine/on your build server (Jenkins etc.).

With Testcontainers you can use a `@ClassRule` or `@Rule` on each of your integration tests and define the Docker image for your test (valid for JUnit 4.12). 

For **MySQL** and **PostgreSQL** and there are already built-in solutions but you are free to use an image of your choice like the following:

```java
// generic container for self-defined Docker images
@ClassRule
public static GenericContainer redis = new GenericContainer("redis:3.0.6").withExposedPorts(6379);

// built-in containers
@ClassRule
public static PostgreSQLContainer postgreSQLContainer = new PostgreSQLContainer().withPassword("inmemory")
            .withUsername("inmemory");
```

To run the integrations tests after your unit tests, simply add `maven-failsafe-plugin` to your project. In addition, make sure your integration tests have `IT` as a postfix:

```xml
<build>
  <plugins>
    <plugin>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-maven-plugin</artifactId>
    </plugin>
    <plugin>
      <groupId>org.apache.maven.plugins</groupId>
      <artifactId>maven-failsafe-plugin</artifactId>
      <version>3.0.0-M4</version>
      <executions>
        <execution>
          <goals>
            <goal>integration-test</goal>
            <goal>verify</goal>
          </goals>
        </execution>
      </executions>
    </plugin>
  </plugins>
</build>
```

## Basic application integration test with Testcontainers

_Using: JUnit 4.12 and Spring Boot < 2.2.6_

Let's start with the integration test each Spring Boot application contains out-of-the-box. This integration test verifies that Spring can create the context and start the application. 

As our application requires a PostgreSQL to be available during startup, we can provide one using Testcontainers. Overriding the properties to use the PostgreSQL database spawned by Testcontainers is as easy as the following:

```java
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@ContextConfiguration(initializers = IntegrationTest.Initializer.class)
public class ApplicationIT {

    @ClassRule
    public static PostgreSQLContainer postgreSQLContainer = new PostgreSQLContainer().withPassword("inmemory")
            .withUsername("inmemory");

    public static class Initializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {

        @Override
        public void initialize(ConfigurableApplicationContext configurableApplicationContext) {
            TestPropertyValues values = TestPropertyValues.of(
                    "spring.datasource.url=" + postgreSQLContainer.getJdbcUrl(),
                    "spring.datasource.password=" + postgreSQLContainer.getPassword(),
                    "spring.datasource.username=" + postgreSQLContainer.getUsername()
            );
            values.applyTo(configurableApplicationContext);
        }
    }

    @Ŧest
    public void contextLoads() {
    }
}
```

## Basic application integration test with JUnit 5 and Spring Boot > 2.2.6

If your application uses JUnit 5, you can't use the `@ClassRule` anymore. Fortunately, Testcontainers provides a solution to write tests with JUnit Jupiter:

```xml
<dependency>
  <groupId>org.testcontainers</groupId>
  <artifactId>junit-jupiter</artifactId>
  <version>${testcontainers.version}</version>
  <scope>test</scope>
</dependency>
```

With this dependency and a more recent version of Spring Boot (> 2.2.6) the basic integration test looks like the following:

```java
// JUnit 5 example with Spring Boot >= 2.2.6
@Testcontainers
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
public class ApplicationIT {

  @Container
  public static PostgreSQLContainer postgreSQLContainer = new PostgreSQLContainer()
    .withPassword("inmemory")
    .withUsername("inmemory");

  @DynamicPropertySource
  static void postgresqlProperties(DynamicPropertyRegistry registry) {
    registry.add("spring.datasource.url", postgreSQLContainer::getJdbcUrl);
    registry.add("spring.datasource.password", postgreSQLContainer::getPassword);
    registry.add("spring.datasource.username", postgreSQLContainer::getUsername);
  }

  @Test
  public void contextLoads() {
  }

}
```

## Integration test with JUnit 5 and Spring Boot < 2.2.6

If your application makes use of JUnit 5 but is using a Spring Boot version < 2.2.6, you don't have access to the `@DynamicPropertySource` feature. 

A possible integration test to verify a REST API endpoint is working as expected looks like the following:

```java
// JUnit 5 example with Spring Boot < 2.2.6
@Testcontainers
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
@ContextConfiguration(initializers = DeletePersonIT.Initializer.class)
public class DeletePersonIT {

  @Container
  public static PostgreSQLContainer postgreSQLContainer = new PostgreSQLContainer()
    .withPassword("inmemory")
    .withUsername("inmemory");

  @Autowired
  private PersonRepository personRepository;

  @Autowired
  public TestRestTemplate testRestTemplate;

  public static class Initializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {

    @Override
    public void initialize(ConfigurableApplicationContext configurableApplicationContext) {
      TestPropertyValues values = TestPropertyValues.of(
        "spring.datasource.url=" + postgreSQLContainer.getJdbcUrl(),
        "spring.datasource.password=" + postgreSQLContainer.getPassword(),
        "spring.datasource.username=" + postgreSQLContainer.getUsername()
      );
      values.applyTo(configurableApplicationContext);
    }
  }

  @Test
  @Sql("/testdata/FILL_FOUR_PERSONS.sql")
  public void testDeletePerson() {
    testRestTemplate.delete("/api/persons/1");
    assertEquals(3, personRepository.findAll().size());
    assertFalse(personRepository.findAll().contains("Phil"));

  }
}
```

You can find more integration test examples for this demo Spring Boot CRUD API application using PostgreSQL on [GitHub](https://github.com/rieckpil/blog-tutorials/tree/master/spring-boot-integration-tests-testcontainers). 

Further integration test-related tutorials for Spring Boot:

* [Spring Boot Functional Tests with Selenium and Testcontainers](https://rieckpil.de/spring-boot-functional-tests-with-selenium-and-testcontainers/)
* [Spring Boot Integration Tests with WireMock and JUnit 5](https://rieckpil.de/spring-boot-integration-tests-with-wiremock-and-junit-5/)

Happy integration-testing with Spring Boot, Testcontainers and JUnit, 
Phil