Enterprise Java

Testcontainers JDBC Support

Testcontainers is a powerful library designed to simplify the testing of database interactions, especially when dealing with different environments and configurations. It enables the creation and management of Docker containers for testing purposes. This article will delve into Testcontainers’ JDBC support by comparing two approaches: programmatic management of Testcontainer lifecycles and using Testcontainers’ streamlined JDBC configuration.

1. Programmatic Management of Testcontainers

In this approach, we manually manage the lifecycle of the Testcontainer. This gives us fine-grained control but requires more boilerplate code.

Spring Initializr offers a quick and easy way to bootstrap a Spring Boot project with all the necessary dependencies. To integrate PostgreSQL with Testcontainers for integration tests, visit Spring Initializr, configure your project, and then add the following dependencies for a sample setup of a PostgreSQL database:

  • Spring Data JPA: Provides JPA support in the Spring Boot project.
  • PostgreSQL Driver: Enables PostgreSQL database connectivity.
  • Testcontainers: Add this dependency to include Testcontainers in the project.

The generated pom.xml will contain the required dependencies. Update the pom.xml as shown below, and here’s an example of how it might look:

        <!-- Testcontainers -->
        <dependency>
            <groupId>org.testcontainers</groupId>
            <artifactId>testcontainers</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.testcontainers</groupId>
            <artifactId>postgresql</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-testcontainers</artifactId>
            <scope>test</scope>
        </dependency>

        <!-- PostgreSQL Driver -->
        <dependency>
            <groupId>org.postgresql</groupId>
            <artifactId>postgresql</artifactId>
        </dependency>

        <!-- Spring Data JPA -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>

Next, we will set up the Testcontainer by creating a test class and configuring the PostgreSQL container.

@SpringBootTest
class SpringbootTestcontainersApplicationTests {

    static PostgreSQLContainer<?> postgresContainer = new PostgreSQLContainer<>("postgres:16-alpine")
            .withDatabaseName("testdb")
            .withUsername("test")
            .withPassword("test");

    @Autowired
    EmployeeRepository employeeRepository;

    @BeforeAll
    public static void setUp() {
        postgresContainer.start();
    }

    @AfterAll
    public static void tearDown() {
        if (postgresContainer != null) {
            postgresContainer.stop();
        }
    }

    @DynamicPropertySource
    static void postgresqlProperties(DynamicPropertyRegistry registry) {
        registry.add("spring.datasource.url", () -> postgresContainer.getJdbcUrl());
        registry.add("spring.datasource.driverClassName", () -> postgresContainer.getDriverClassName());
        registry.add("spring.datasource.username", () -> postgresContainer.getUsername());
        registry.add("spring.datasource.password", () -> postgresContainer.getPassword());

    }

    @Test
    public void testMySQLConnection() throws Exception {
        employeeRepository.save(new Employee("Josh Fish", "josh@jcg.com"));

        assertThat(employeeRepository.findAll())
                .hasSize(1).first()
                .extracting(Employee::getEmail)
                .isEqualTo("josh@jcg.com");

        System.out.println("" + postgresContainer.getLogs());
    }
}

In this example, the PostgreSQLContainer is started before each test and stopped afterwards. The method postgresqlProperties is annotated with @DynamicPropertySource, which allows us to dynamically set the Spring datasource properties (url, driverClassName, username, and password) at runtime, based on the properties of the PostgreSQL container.

By using @DynamicPropertySource, we avoid hardcoding the JDBC connection details, especially the port. This ensures that the test environment is flexible and less prone to errors due to port conflicts. This dynamic setup also allows for smoother integration with other services and containers that might be running concurrently.

2. Testcontainers JDBC Support

While the programmatic approach is powerful, Testcontainers also offers a more streamlined way to manage containers in tests through JDBC support. By using a simple configuration property, we can simplify the setup and allow the framework to handle the lifecycle management for us.

In our application.properties or application.yml file, we need to specify the Testcontainers JDBC URL to enable automatic container management for the database:

spring.datasource.url=jdbc:tc:postgresql:16-alpine:///testdb
spring.jpa.hibernate.ddl-auto=create

By leveraging the jdbc:tc: prefix in the JDBC URL (spring.datasource.url), Testcontainers automatically handles the lifecycle of the PostgreSQL container. The container starts up before the test and shuts down afterwards without any manual intervention.

Now, we can write a simple Spring Boot test that uses this configuration:

@SpringBootTest
public class CustomTestcontainerTests {

    @Autowired
    EmployeeRepository employeeRepository;

    @Test
    public void testPostgreSQLConnection() {

        employeeRepository.save(new Employee("Josh Fish", "josh@jcg.com"));

        assertThat(employeeRepository.findAll())
                .hasSize(1).first()
                .extracting(Employee::getEmail)
                .isEqualTo("josh@jcg.com");

    }
}

This method significantly reduces the amount of setup code, making it an ideal choice for quick and straightforward tests.

3. Comparison of the Two Approaches

FeatureProgrammatic Lifecycle ManagementTestcontainers JDBC Support
Control over LifecycleHigh control; manual start/stop of containersLow control; lifecycle managed automatically
Setup ComplexityRequires setup and teardown methodsMinimal setup; just a JDBC URL configuration
Integration with SpringRequires manual configurationSeamless integration with Spring Boot
Use CaseIdeal for complex scenarios needing controlIdeal for simple, quick integration tests

4. Conclusion

In this article, we explored two different approaches to using Testcontainers JDBC support for managing Docker containers in your integration tests. We started by examining how to programmatically manage the lifecycle of containers, offering full control and flexibility over container management. We then compared this with a more streamlined approach, utilizing Testcontainers JDBC support to simplify the setup through a single configuration property.

5. Download the Source Code

This article explores the JDBC integration features of Testcontainers.

Download
You can download the full source code of this example here: Testcontainers JDBC integration

Omozegie Aziegbe

Omos holds a Master degree in Information Engineering with Network Management from the Robert Gordon University, Aberdeen. Omos is currently a freelance web/application developer who is currently focused on developing Java enterprise applications with the Jakarta EE framework.
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Back to top button