When using a database in Java integration tests, some of the popular choices are:
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:
A GitHub sample repository link is provided at the end of this article.
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.
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é