-
Notifications
You must be signed in to change notification settings - Fork 1
Testcontainers
TestContainers is a library that helps you run module-specific Docker containers to simplify Integration Testing.
These Docker containers are lightweight, and once the tests are finished the containers get destroyed.
- Test containers is a Java library that provides functionality to handle a docker container. We can start any container by using the GenericContainer with any docker image, one of the specialized containers (e.g PostgreSqlContainer) provided by a module, or by programmatically creating our own image on the fly.
- To bind the container to the lifecycle of our JUnit tests, we can use the provided integration. Test containers also ensure that the application inside the container is started and ready to use, by providing a WaitStrategy.
- TestContainers downloads the MySQL, Postgres, Kafka, and Redis images and runs in a container. The MySQL container will run a MySQL Database in a container and the test cases can connect to it on the local machine. Once the execution is over the Database will be gone – it just deletes from the machine. In the test cases, we can start as many container images as we want.
- TestContainers supports JUnit 4, JUnit 5, and Spock
- Integration tests will point to the same version of the database as it’s in production. So we can tie our TestContainer Database Image to the same version running on production.
- Integration tests are a lot more reliable because both applications and tests are using the same database type and version and there won't be any compatibility issues in test cases.
We are setting up the template project by using Java 17, Spring Data JPA, PostgreSQL, and Gradle/Maven. and we are using JUnit 5, Testcontainers, and RestAssured for testing.
Following are the Test containers and RestAssured dependencies:
build.gradle
ext {
set('testcontainersVersion', "1.17.6")
}
dependencies {
...
...
testImplementation 'org.testcontainers:junit-jupiter'
testImplementation 'org.testcontainers:postgresql'
testImplementation 'io.rest-assured:rest-assured'
}
dependencyManagement {
imports {
mavenBom "org.testcontainers:testcontainers-bom:${testcontainersVersion}"
}
}
Testcontainers library can be used to spin up desired services as docker containers and run tests against those services. We can use our testing library lifecycle hooks to start/stop containers using Testcontainers API.
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class TodoControllerTests {
@LocalServerPort
private Integer port;
static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:14-alpine");
@BeforeAll
static void beforeAll() {
postgres.start();
}
@AfterAll
static void afterAll() {
postgres.stop();
}
@DynamicPropertySource
static void configureProperties(DynamicPropertyRegistry registry) {
registry.add("spring.datasource.url", postgres::getJdbcUrl);
registry.add("spring.datasource.username", postgres::getUsername);
registry.add("spring.datasource.password", postgres::getPassword);
}
@Autowired
TodoRepository todoRepository;
@BeforeEach
void setUp() {
todoRepository.deleteAll();
RestAssured.baseURI = "http://localhost:" + port;
}
@Test
void shouldGetAllTodos() {
List<Todo> todos = List.of(
new Todo(null, "Todo Item 1", false, 1),
new Todo(null, "Todo Item 2", false, 2)
);
todoRepository.saveAll(todos);
given()
.contentType(ContentType.JSON)
.when()
.get("/todos")
.then()
.statusCode(200)
.body(".", hasSize(2));
}
}
Here we have defined a PostgreSQLContainer
instance, started the container before executing tests and stopped it after executing all the tests using JUnit 5 test lifecycle hook methods.
The Postgresql container port (5432) will be mapped to a random available port on the host. This helps to avoid port conflicts and allows running tests in parallel. Then we are using SpringBoot's dynamic property registration support to add/override the datasource
properties obtained from the Postgres container.
@DynamicPropertySource
static void configureProperties(DynamicPropertyRegistry registry) {
registry.add("spring.datasource.url", postgres::getJdbcUrl);
registry.add("spring.datasource.username", postgres::getUsername);
registry.add("spring.datasource.password", postgres::getPassword);
}
In shouldGetAllTodos()
test we are saving two Todo entities into the database using TodoRepository and testing GET /todos API endpoint to fetch todos using RestAssured.
We can run the tests directly from IDE or using the command ./gradlew test
from the terminal.
Instead of implementing JUnit 5 lifecycle callback methods to start and stop the Postgres container, we can use Testcontainers JUnit 5 Extension annotations to manage the container lifecycle as follows:
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@Testcontainers
public class TodoControllerTests {
@Container
static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:14-alpine");
@DynamicPropertySource
static void configureProperties(DynamicPropertyRegistry registry) {
registry.add("spring.datasource.url", postgres::getJdbcUrl);
registry.add("spring.datasource.username", postgres::getUsername);
registry.add("spring.datasource.password", postgres::getPassword);
}
}
The Testcontainers JUnit 5 Extension will take care of starting the container before tests and stopping it after tests. If the container is a static field then it will be started once before all the tests and stopped after all the tests. If it is a non-static field then the container will be started before each test and stopped after each test.
Even if we don't stop the containers explicitly, Testcontainers will take care of removing the containers, using ryuk
container behind the scenes, once all the tests are done. But it is recommended to clean up the containers as soon as possible.
Testcontainers provides special jdbc url support which automatically spins up the configured database as a container.
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@TestPropertySource(properties = {
"spring.datasource.url=jdbc:tc:postgresql:14-alpine:///todos"
})
class ApplicationTests {
@Test
void contextLoads() {
}
}
By setting the datasource url to jdbc:tc:postgresql:14-alpine:///todos
(notice the special :tc
prefix), Testcontainers automatically spin up the Postgres database using postgresql:14-alpine
docker image.
For more information on Testcontainers JDBC Support refer https://www.testcontainers.org/modules/databases/jdbc/
Verify the test cases by using
$ ./gradlew test


Testcontainers enable using the real dependencies services like SQL databases, NoSQL datastores, and message brokers or any containerized services for that matter. This approach allows us to create reliable test suites improving confidence in our code.