In previous lectures we dove into testing microservice applications. We went through different approaches and types of testing, including unit testing with JUnit and Mockito, integration testing of REST APIs using MockMvc, and we also covered contract testing with Pact. We started picking tools that help ensure reliable interactions between components and services. Now it's time to look at how to test interactions with databases using a Docker-based testing tool — Testcontainers.
Why use Testcontainers?
Imagine you're testing database interactions and someone accidentally inserts invalid data into the test DB. Your tests fail, you start debugging, and it's mess. Or another scenario: you're testing against PostgreSQL locally, but production runs on MySQL. What do you do? The test environment should be:
- Isolated — so every test run doesn't depend on external factors.
- Realistic — so tests run with the same settings as production.
- Easy to set up — so it doesn't require complex infra.
Testcontainers lets you run Docker containers during test execution, providing an isolated and realistic environment for testing. That means you can use the same databases, message brokers, and other dependencies that are used in the real world. Ready? Let's go!
Getting started with Testcontainers
- Let's add dependencies
First, connect Testcontainers to your project. If you're using PostgreSQL (a tester favorite), your dependencies in
pom.xmlwill look like this:<dependencies> <dependency> <groupId>org.testcontainers</groupId> <artifactId>junit-jupiter</artifactId> <version>1.19.0</version> <scope>test</scope> </dependency> <dependency> <groupId>org.testcontainers</groupId> <artifactId>postgresql</artifactId> <version>1.19.0</version> <scope>test</scope> </dependency> </dependencies>If you're using Gradle:
testImplementation 'org.testcontainers:junit-jupiter:1.19.0' testImplementation 'org.testcontainers:postgresql:1.19.0'Testcontainers supports many other databases like MySQL, MongoDB, Oracle, and even Kafka and RabbitMQ. You can find the full list in the official Testcontainers documentation.
- Make sure Docker is installed Testcontainers works through Docker, so make sure it's installed and running on your machine.
- Minimal Docker configuration Make sure Docker can run database containers. A quick check is to run the
docker runcommand directly for PostgreSQL:docker run --name postgres-test -e POSTGRES_PASSWORD=test -d postgres:latestIf the container starts without errors, you're good to go!
Setting up a test using Testcontainers
1. Starting a PostgreSQL container
Let's start by creating the simplest PostgreSQL container for tests. Here's an example:
import org.junit.jupiter.api.Test;
import org.testcontainers.containers.PostgreSQLContainer;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class PostgresContainerTest {
@Test
void testPostgresContainer() {
// Create PostgreSQL container
try (PostgreSQLContainer
postgresContainer = new PostgreSQLContainer<>("postgres:latest")) {
postgresContainer.start();
// Check that the DB is up
String jdbcUrl = postgresContainer.getJdbcUrl();
String username = postgresContainer.getUsername();
String password = postgresContainer.getPassword();
System.out.println("JDBC URL: " + jdbcUrl);
System.out.println("User: " + username);
System.out.println("Password: " + password);
assertEquals("test", username);
}
}
}
This test spins up a PostgreSQL container, gets the connection URL, and shows how you can use it. Sure, it's not super useful yet, but it's a start.
2. Integration with Spring Data JPA
Now we'll tie Testcontainers to your Spring Boot app. Let's say your app uses JPA, and in tests we'll bring up a PostgreSQL container.
Configuration of application.properties for tests
Create a file application-test.properties under src/test/resources:
spring.datasource.url=jdbc:tc:postgresql:latest:///testdb
spring.datasource.username=test
spring.datasource.password=test
spring.jpa.database-platform=org.hibernate.dialect.PostgreSQLDialect
jdbc:tc:postgresql:latest:///testdb — this is a bit of Testcontainers magic. It will automatically spin up a PostgreSQL container and hook up its data.
Example test using JPA
Suppose you have an entity User and a repository UserRepository:
@Entity
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
// Constructors, getters and setters
}
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
}
Let's create an integration test for the repository:
@SpringBootTest
@TestPropertySource(locations = "classpath:application-test.properties")
public class UserRepositoryTest {
@Autowired
private UserRepository userRepository;
@Test
void testSaveUser() {
// Create a new user
User user = new User();
user.setName("Test User");
// Save it to the DB
User savedUser = userRepository.save(user);
// Check the result
assertNotNull(savedUser.getId());
assertEquals("Test User", savedUser.getName());
}
}
Now, when you run the test, Spring Boot will automatically spin up a PostgreSQL container via Testcontainers.
3. Manual control of the container
In complex scenarios you might want to manage the container manually. For example:
@Testcontainers
@SpringBootTest
@TestPropertySource(locations = "classpath:application-test.properties")
public class ManualPostgresContainerTest {
@Container
private static PostgreSQLContainer
postgresContainer = new PostgreSQLContainer<>("postgres:latest");
@DynamicPropertySource
static void registerProperties(DynamicPropertyRegistry registry) {
registry.add("spring.datasource.url", postgresContainer::getJdbcUrl);
registry.add("spring.datasource.username", postgresContainer::getUsername);
registry.add("spring.datasource.password", postgresContainer::getPassword);
}
@Autowired
private UserRepository userRepository;
@Test
void testSaveUserManualContainer() {
User user = new User();
user.setName("Another User");
User savedUser = userRepository.save(user);
assertNotNull(savedUser.getId());
assertEquals("Another User", savedUser.getName());
}
}
Here @Container manages the container lifecycle, and @DynamicPropertySource lets you register dynamic properties into the Spring context.
Common errors and how to fix them
- Docker isn't running If Docker isn't up, Testcontainers simply won't be able to spin up containers. Make sure Docker is running properly.
- Network issues Sometimes containers can't interact with the app because of network settings. Check if you're running in a restricted Docker mode (for example, with no internet).
- Not enough resources Databases can be heavy for a local machine. Make sure your machine has enough memory and CPU to run them.
Practical use
You just saw how Testcontainers can make life easier for developers and testers. Testing with real databases is now accessible and simple, and tests become more reliable. This tool is especially useful in interviews where it's important to show confidence in testing microservices. In real projects it helps avoid integration issues with production systems.
For further reading, check out the Testcontainers documentation.
GO TO FULL VERSION