Article

Switching between H2 and Testcontainers in Spring tests

Why choose between H2 and Testcontainers for the database of your Spring Boot tests when you can have both?

H2 Testcontainers Spring Test
Intermediate
Florian Beaufumé
Florian Beaufumé
Published 10 Mar 2024 - Last updated 23 Mar 2024 - 3 min read
Switching between H2 and Testcontainers in Spring tests

Table of contents

Introduction

When using a database in Java integration tests, some of the popular choices are:

  • Use an in-memory database such as H2. Easy to setup and fast to execute, this is the most convenient approach. But it is not as representative as using your regular database.
  • Use Testcontainers to automatically create temporary instances of your regular database, for the duration of the tests, thanks to Docker. This increases the tests representativeness, but needs some extra configuration, is slower than using an in-memory database and of course requires Docker.

It is possible to have the best of both worlds. For example, use H2 during local development (for increased performances) and Testcontainers in the CI/CD pipeline (for increased representativeness) by simply changing a command line parameter.

There are different ways to do so. When using the default Spring configuration, this is simple. But in Spring tests using @ActiveProfiles it's a bit more complicated. That's what this article describes.

Note that this article is part of a test series:

  1. Getting started with Spring tests provides best practices and tips to get started with Spring tests.
  2. Switching between H2 and Testcontainers in Spring tests (this article) describes how to easily switch between H2 and Testcontainers in Spring tests.
  3. Security annotations in Spring tests focuses on security annotations used in Spring tests.

A GitHub sample repository link is provided at the end of this article.

Switching between H2 and Testcontainers

For simple applications, using a dedicated test profile may not be useful. But for more complex applications, I often use such profile, usually named test, along with a dedicated configuration file, application-test.properties. This profile is usually enabled using @ActiveProfiles("test") in the test classes.

This section shows how we can dynamically change the active profiles, in order to switch between H2 and Testcontainers.

First, in the pom.xml file, we declare dependencies to the regular database (I chose PostgreSQL), H2, Testcontainers and other needed libraries (Spring, various test dependencies, etc):

<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<scope>runtime</scope>
</dependency>

<!-- Other non-test dependencies, such as Spring libraries, as needed -->

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>postgresql</artifactId>
<version>1.19.5</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>junit-jupiter</artifactId>
<scope>test</scope>
</dependency>

<!-- Other test dependencies, as needed -->

Then, I use two Spring profiles in the test classes. The test profile is selected by default and uses H2, while the postgres profile must be explicitly selected and uses Testcontainers and PostgreSQL.

The application-test.properties Spring configuration file contains the H2 configuration and all other test configuration parameters:

spring.datasource.url=jdbc:h2:mem:mydatabase;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE

# Other test properties, as needed

The application-postgres.properties Spring configuration file describes the PostgreSQL configuration for Testcontainers:

spring.datasource.url=jdbc:tc:postgresql:16-alpine:///mydatabase

The JDBC URL actually contains the version of the PostgreSQL Docker image to use.

Now we can point to the right profile in the Spring Boot test classes using the @ActiveProfiles annotation:

@SpringBootTest
@ActiveProfiles(value = "test", resolver = CustomActiveProfilesResolver.class)
class MyServiceTest {

// Some test methods
}

In @ActiveProfiles(...) the profile is set to test, meaning that H2 will be used by default. You can see that a custom resolver is declared. That's where the magic happens. The resolver dynamically alters the profiles used by the test class. Here is the resolver implementation:

public class CustomActiveProfilesResolver implements ActiveProfilesResolver {

private final DefaultActiveProfilesResolver defaultActiveProfilesResolver = new DefaultActiveProfilesResolver();

@Override
public String[] resolve(Class<?> testClass) {
return Optional.ofNullable(System.getProperty("test.profiles"))
.map(p -> p.split("\\s*,\\s*"))
.orElseGet(() -> defaultActiveProfilesResolver.resolve(testClass));
}
}

The implementation simply checks if a system property test.profiles is set. If it is, it uses the value as the active profiles. If not, it uses the default resolver that sticks to the test profile.

That's it. Now we can run the tests from the command line with H2 using mvn test or with Testcontainers and PostgreSQL using mvn test -Dtest.profiles=test,postgres.

A truncated output of mvn test:

12:06:35.412+01:00  INFO c.a.s.MyServiceTest   : The following 1 profile is active: "test"
(...)
12:06:39.084+01:00 INFO c.z.h.p.HikariPool : HikariPool-1 - Added connection conn0: url=jdbc:h2:mem:mydatabase user=SA

A truncated output of mvn test -Dtest.profiles=test,postgres:

12:09:23.364+01:00  INFO c.a.s.MyServiceTest   : The following 2 profiles are active: "test", "postgres"
(...)
12:09:28.456+01:00 INFO tc.postgres:16-alpine : Creating container for image: postgres:16-alpine
12:09:30.976+01:00 INFO tc.postgres:16-alpine : Container postgres:16-alpine started in PT2.5208665S
12:09:31.149+01:00 INFO c.z.h.p.HikariPool : HikariPool-1 - Added connection org.testcontainers.jdbc.ConnectionWrapper@268e30d4

Of course, we can also run the tests from the IDE with H2 or Testcontainers, by setting or not the system property test.profiles to test,postgres in the run configuration.

Conclusion

In this article I showed how we can easily switch between H2 and Testcontainers when running Spring Boot integration tests that use @ActiveProfiles.

A sample project is available in GitHub, see spring-tests. Note that the sample project and this article have one main difference: this article assumes that the main database is PostgreSQL, while the sample project uses H2 (for convenience) as the main database. The principles are the same, though.

© 2007-2024 Florian Beaufumé