Bird
Raised Fist0
JUnittesting~15 mins

Test containers for database testing in JUnit - Build an Automation Script

Choose your learning style10 modes available
Verify data insertion and retrieval using Testcontainers with PostgreSQL
Preconditions (3)
Step 1: Start a PostgreSQL container using Testcontainers
Step 2: Connect to the PostgreSQL database using JDBC
Step 3: Create a table named 'users' with columns 'id' (int) and 'name' (varchar)
Step 4: Insert a record with id=1 and name='Alice' into the 'users' table
Step 5: Query the 'users' table to retrieve the record with id=1
Step 6: Verify that the retrieved name is 'Alice'
✅ Expected Result: The test should pass confirming that the record was inserted and retrieved correctly from the PostgreSQL container database
Automation Requirements - JUnit 5 with Testcontainers
Assertions Needed:
Assert that the retrieved name equals 'Alice'
Best Practices:
Use @Testcontainers and @Container annotations to manage container lifecycle
Use explicit waits or retries if needed for container readiness
Use try-with-resources for JDBC connections and statements
Keep test isolated and independent
Automated Solution
JUnit
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.

Common Mistakes - 4 Pitfalls
Not using @Container annotation and manually starting/stopping containers
Hardcoding JDBC URL, username, or password instead of using container getters
Not closing JDBC connections, statements, or result sets
Assuming the container is ready immediately after start without waiting
Bonus Challenge

Now add data-driven testing with 3 different user records (id and name) to verify insertion and retrieval for each.

Show Hint

Practice

(1/5)
1. What is the main advantage of using Testcontainers for database testing in JUnit?
easy
A. It provides a real database environment isolated for each test run.
B. It replaces the need for any database in tests with mocks.
C. It automatically writes test cases for database queries.
D. It speeds up tests by using in-memory databases only.

Solution

  1. Step 1: Understand what Testcontainers provide

    Testcontainers run real database instances inside containers, giving a fresh environment for each test.
  2. Step 2: Compare with other options

    Mocks or in-memory databases do not provide real database behavior, and Testcontainers do not auto-generate tests.
  3. Final Answer:

    It provides a real database environment isolated for each test run. -> Option A
  4. Quick Check:

    Real isolated DB environment = A [OK]
Hint: Testcontainers run real DBs isolated per test [OK]
Common Mistakes:
  • Thinking Testcontainers use mocks instead of real DBs
  • Confusing Testcontainers with in-memory databases
  • Believing Testcontainers auto-generate tests
2. Which of the following is the correct way to start a PostgreSQL container using Testcontainers in a JUnit test?
easy
A. PostgreSQLContainer postgres = new PostgreSQLContainer("latest:postgres").launch();
B. PostgreSQLContainer postgres = new PostgreSQLContainer("postgres:latest").run();
C. PostgreSQLContainer postgres = new PostgreSQLContainer("postgres").begin();
D. PostgreSQLContainer postgres = new PostgreSQLContainer<>("postgres:latest").start();

Solution

  1. 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 with start().
  2. Step 2: Validate method names and image format

    Methods like run(), begin(), or launch() do not exist. The image name format must be "postgres:latest".
  3. Final Answer:

    PostgreSQLContainer postgres = new PostgreSQLContainer<>("postgres:latest").start(); -> Option D
  4. Quick Check:

    Use start() with correct image name [OK]
Hint: Use start() method with correct image name [OK]
Common Mistakes:
  • Using wrong method names like run(), begin(), or launch()
  • Incorrect image name format
  • Omitting generic type parameter
3. Given the following JUnit test snippet using Testcontainers, what will be the output of System.out.println(postgres.getJdbcUrl());?
PostgreSQLContainer postgres = new PostgreSQLContainer<>("postgres:13-alpine").start();
System.out.println(postgres.getJdbcUrl());
postgres.stop();
medium
A. The literal string "postgres:13-alpine"
B. An error because getJdbcUrl() is not available
C. A JDBC URL string like jdbc:postgresql://localhost:randomPort/test
D. Null, because the container is stopped immediately

Solution

  1. Step 1: Understand what getJdbcUrl() returns

    After starting, getJdbcUrl() returns the connection URL to the running container database.
  2. 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.
  3. Final Answer:

    A JDBC URL string like jdbc:postgresql://localhost:randomPort/test -> Option C
  4. Quick Check:

    getJdbcUrl() after start() returns valid URL [OK]
Hint: Call getJdbcUrl() only after container start() [OK]
Common Mistakes:
  • Calling getJdbcUrl() before start()
  • Expecting literal image name as URL
  • Assuming output is null after stop() immediately
4. Identify the error in this JUnit test using Testcontainers for MySQL:
@Test
void testDbConnection() {
  MySQLContainer mysql = new MySQLContainer("mysql:8.0");
  mysql.start();
  String url = mysql.getJdbcUrl();
  // Use url to connect
  mysql.stop();
  mysql.stop();
}
medium
A. Calling mysql.stop() twice causes an error.
B. Missing generic type parameter in MySQLContainer declaration.
C. Container must be started with run() instead of start().
D. The image name "mysql:8.0" is invalid.

Solution

  1. Step 1: Check MySQLContainer declaration

    MySQLContainer is a generic class and should be declared with a wildcard: MySQLContainer<?>.
  2. Step 2: Verify other parts

    Calling stop() twice is safe (idempotent). start() is correct method. Image name "mysql:8.0" is valid.
  3. Final Answer:

    Missing generic type parameter in MySQLContainer declaration. -> Option B
  4. Quick Check:

    Use MySQLContainer to avoid raw type warning [OK]
Hint: Always use generic type with Testcontainers classes [OK]
Common Mistakes:
  • Calling stop() multiple times causes error
  • Using run() instead of start()
  • Incorrect image name format
5. You want to write a JUnit test that uses a PostgreSQL Testcontainer and runs a SQL script to initialize the database before tests. Which approach correctly achieves this?
hard
A. Use withInitScript("init.sql") on the PostgreSQLContainer instance before starting it.
B. Manually connect after start() and run the SQL script using JDBC in each test method.
C. Include the SQL script inside the Docker image and use a custom image name.
D. Start the container, then copy the SQL file into the container using copyFileToContainer() and run it manually.

Solution

  1. Step 1: Identify Testcontainers feature for init scripts

    Testcontainers provide withInitScript() method to run SQL scripts automatically on container startup.
  2. 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.
  3. Final Answer:

    Use withInitScript("init.sql") on the PostgreSQLContainer instance before starting it. -> Option A
  4. Quick Check:

    Use withInitScript() for automatic DB initialization [OK]
Hint: Use withInitScript() to auto-run SQL on container start [OK]
Common Mistakes:
  • Running SQL manually in each test instead of init script
  • Modifying Docker images unnecessarily
  • Copying files manually instead of using built-in methods
Recommended

Ready to prove it?

Pass the test and this pattern moves unknown strong while earning +1 Grip toward your certificate.

10 questions~5 mineasy hard