One of the reasons why Spring and Spring Boot are so popular is their good testing support . You can write both unit tests with Mockito without using Spring functionality, and integration tests with the initialization of the Spring context.
Integration tests may require interaction with external services such as relational databases, NoSQL databases, Kafka, and others. When testing, it is convenient to deploy these services in Docker containers.
Testcontainers
From Testcontainers documentation:
TestContainers is a Java library that supports JUnit tests by providing lightweight, transient instances for popular databases, web browsers with Selenium, and anything else that can run in a Docker container.
Using Testcontainers, you can start a Singleton Docker container as follows:
@SpringBootTest
@ContextConfiguration(initializers = {UserServiceIntegrationTest.Initializer.class})
class UserServiceIntegrationTest {
private static PostgreSQLContainer sqlContainer;
static {
sqlContainer = new PostgreSQLContainer("postgres:10.7")
.withDatabaseName("integration-tests-db")
.withUsername("sa")
.withPassword("sa");
sqlContainer.start();
}
static class Initializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
public void initialize(ConfigurableApplicationContext configurableApplicationContext) {
TestPropertyValues.of(
"spring.datasource.url=" + sqlContainer.getJdbcUrl(),
"spring.datasource.username=" + sqlContainer.getUsername(),
"spring.datasource.password=" + sqlContainer.getPassword()
).applyTo(configurableApplicationContext.getEnvironment());
}
}
@Autowired
private UserService userService;
@Test
void shouldGetAllUsers() {
// test userService.getAllUsers()
}
}
Since this is used quite often, a starter was created by the community to simplify life by the community - Testcontainers Spring Boot Starter .
Testcontainers SpringBoot Starter
Testcontainers - The starter depends on the spring-cloud-starter . If your application does not use SpringCloud starters, then you need to add spring-cloud-starter as a test dependency.
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter</artifactId>
<scope>test</scope>
</dependency>
And also add the library for the database. For example, if you want to use Postgresql:
<dependency>
<groupId>com.playtika.testcontainers</groupId>
<artifactId>embedded-postgresql</artifactId>
<scope>test</scope>
</dependency>
When added
embedded-postgresql
to the environment, the following properties will be available:
embedded.postgresql.port
embedded.postgresql.host
embedded.postgresql.schema
embedded.postgresql.user
embedded.postgresql.password
They can be used to set up a datasource.
Typically, Docker containers are only used for integration tests, not unit tests. With the help of profiles, we can disable them by default and enable them only for integration tests.
src/test/resources/bootstrap.properties
embedded.postgresql.enabled=false
src/test/resources/bootstrap-integration-test.properties
embedded.postgresql.enabled=true
spring.datasource.driver-class-name=org.postgresql.Driver
spring.datasource.url=jdbc:postgresql://${embedded.postgresql.host}:${embedded.postgresql.port}/${embedded.postgresql.schema}
spring.datasource.username=${embedded.postgresql.user}
spring.datasource.password=${embedded.postgresql.password}
Now you can run integration tests with the integration-test profile using
@ActiveProfiles
:
@SpringBootTest
@ActiveProfiles("integration-test")
class UserServiceIntegrationTest {
@Autowired
private UserService userService;
@Test
void shouldGetAllUsers() {
// test userService.getAllUsers()
}
}
You can specify a specific version of the docker image as follows:
src/test/resources/bootstrap-integration-test.properties
embedded.postgresql.dockerImage=postgres:10.7
embedded.postgresql.enabled=true
The testcontainers starter already provides support for the most popular containers such as Postgresql, MariaDB, MongoDB, Redis, RabbitMQ, Kafka, Elasticsearch, and more.
Surprisingly, there is currently no direct support for MySQL. Although there is a simple workaround for this as described here
Refactoring application code in Spring