Test containers for database testing in JUnit - Build an Automation Script
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.testcontainers.containers.PostgreSQLContainer; import org.testcontainers.junit.jupiter.Container; import org.testcontainers.junit.jupiter.Testcontainers; import java.sql.Connection; import java.sql.DriverManager; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.Statement; import static org.junit.jupiter.api.Assertions.assertEquals; @Testcontainers public class PostgresTestcontainersTest { @Container public static PostgreSQLContainer<?> postgresContainer = new PostgreSQLContainer<>("postgres:15.3") .withDatabaseName("testdb") .withUsername("testuser") .withPassword("testpass"); @Test public void testInsertAndRetrieveUser() throws Exception { String jdbcUrl = postgresContainer.getJdbcUrl(); String username = postgresContainer.getUsername(); String password = postgresContainer.getPassword(); try (Connection conn = DriverManager.getConnection(jdbcUrl, username, password)) { try (Statement stmt = conn.createStatement()) { stmt.executeUpdate("CREATE TABLE users (id INT PRIMARY KEY, name VARCHAR(255))"); } try (PreparedStatement insertStmt = conn.prepareStatement("INSERT INTO users (id, name) VALUES (?, ?)");) { insertStmt.setInt(1, 1); insertStmt.setString(2, "Alice"); insertStmt.executeUpdate(); } try (PreparedStatement queryStmt = conn.prepareStatement("SELECT name FROM users WHERE id = ?")) { queryStmt.setInt(1, 1); try (ResultSet rs = queryStmt.executeQuery()) { if (rs.next()) { String name = rs.getString("name"); assertEquals("Alice", name, "The retrieved name should be Alice"); } else { throw new AssertionError("No user found with id=1"); } } } } } }
This test class uses JUnit 5 and Testcontainers to run a PostgreSQL database inside a Docker container.
The @Container annotation manages the lifecycle of the PostgreSQL container automatically.
Inside the test method, it connects to the database using JDBC with credentials provided by the container.
It creates a table, inserts a user record, then queries the record to verify the name matches the expected value.
Try-with-resources ensures all database resources are closed properly.
The assertion checks that the retrieved name is exactly 'Alice', confirming the database operations worked inside the container.
Now add data-driven testing with 3 different user records (id and name) to verify insertion and retrieval for each.
Practice
Testcontainers for database testing in JUnit?Solution
Step 1: Understand what Testcontainers provide
Testcontainers run real database instances inside containers, giving a fresh environment for each test.Step 2: Compare with other options
Mocks or in-memory databases do not provide real database behavior, and Testcontainers do not auto-generate tests.Final Answer:
It provides a real database environment isolated for each test run. -> Option AQuick Check:
Real isolated DB environment = A [OK]
- Thinking Testcontainers use mocks instead of real DBs
- Confusing Testcontainers with in-memory databases
- Believing Testcontainers auto-generate tests
Solution
Step 1: Check the correct constructor and method
The correct constructor uses the image name string and the generic type, and the container is started withstart().Step 2: Validate method names and image format
Methods likerun(),begin(), orlaunch()do not exist. The image name format must be "postgres:latest".Final Answer:
PostgreSQLContainer postgres = new PostgreSQLContainer<>("postgres:latest").start(); -> Option DQuick Check:
Usestart()with correct image name [OK]
start() method with correct image name [OK]- Using wrong method names like run(), begin(), or launch()
- Incorrect image name format
- Omitting generic type parameter
System.out.println(postgres.getJdbcUrl());?
PostgreSQLContainer postgres = new PostgreSQLContainer<>("postgres:13-alpine").start();
System.out.println(postgres.getJdbcUrl());
postgres.stop();Solution
Step 1: Understand what getJdbcUrl() returns
After starting,getJdbcUrl()returns the connection URL to the running container database.Step 2: Check container lifecycle and output
The container is started before printing, so the URL is valid. The container stops after printing, so output is not null or error.Final Answer:
A JDBC URL string like jdbc:postgresql://localhost:randomPort/test -> Option CQuick Check:
getJdbcUrl() after start() returns valid URL [OK]
- Calling getJdbcUrl() before start()
- Expecting literal image name as URL
- Assuming output is null after stop() immediately
@Test
void testDbConnection() {
MySQLContainer mysql = new MySQLContainer("mysql:8.0");
mysql.start();
String url = mysql.getJdbcUrl();
// Use url to connect
mysql.stop();
mysql.stop();
}Solution
Step 1: Check MySQLContainer declaration
MySQLContainer is a generic class and should be declared with a wildcard:MySQLContainer<?>.Step 2: Verify other parts
Callingstop()twice is safe (idempotent).start()is correct method. Image name "mysql:8.0" is valid.Final Answer:
Missing generic type parameter in MySQLContainer declaration. -> Option BQuick Check:
Use MySQLContainer to avoid raw type warning [OK]
- Calling stop() multiple times causes error
- Using run() instead of start()
- Incorrect image name format
Solution
Step 1: Identify Testcontainers feature for init scripts
Testcontainers providewithInitScript()method to run SQL scripts automatically on container startup.Step 2: Compare alternatives
Manual JDBC execution or copying files is more complex and error-prone. Custom images are possible but not necessary for simple init scripts.Final Answer:
Use withInitScript("init.sql") on the PostgreSQLContainer instance before starting it. -> Option AQuick Check:
Use withInitScript() for automatic DB initialization [OK]
- Running SQL manually in each test instead of init script
- Modifying Docker images unnecessarily
- Copying files manually instead of using built-in methods
Ready to prove it?
Pass the test and this pattern moves unknown
