Testcontainers - Integration testing at its best

A breath of fresh air

When creating an integration test you want the test to be as close as possible to the environment where you will eventually want to deploy right ?. One of the components I regularly need for an integration test is a database.

I've managed this in the past using an in memory H2 database. This can work pretty well. but has its drawbacks. One is that it is not exactly the same as the database I will be using for the actual deployment. Lets say i want to run my test against Postgres Version 14.5. I could download this postgres version , install locally and adjust my test properties to point at this. This would work locally on my computer but what about my colleagues. I can't expect them to go through that process also.

Here comes Testcontainers to the rescue. With a few simple changes to the test and configuration i can run the test against a database and version of my choice.

Prerequisites

You should have docker setup on your computer. You can install it by going to docker desktop

Setting up the project

Lets create a spring boot project using spring boot jpa, project lombok, and flyway .Go to spring io and select the setup as below ..

2022-09-23 16.49.55 start.spring.io 176dea4d43b0.png

I have selected java 17, spring boot jpa, flyway, project lombok and testcontainers. Click on generate and load the project in the ide of your choice.

I use flyway to perform db migrations and for this project it just involves creating a simple table in the database.

In the pom.xml for your project the following dependencies are added for testcontainers

        <dependency>
            <groupId>org.testcontainers</groupId>
            <artifactId>testcontainers</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.testcontainers</groupId>
            <artifactId>junit-jupiter</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.testcontainers</groupId>
            <artifactId>postgresql</artifactId>
            <scope>test</scope>
        </dependency>

Create a table

In the folder src/main/resources/db/migration create a file with the name V1__create_country_table.sql

DROP TABLE IF EXISTS public.country;

CREATE TABLE IF NOT EXISTS public.country
(
    iso character(2) COLLATE pg_catalog."default" NOT NULL,

    name character varying(128) COLLATE pg_catalog."default" NOT NULL,
    capital character varying(128) COLLATE pg_catalog."default",

    CONSTRAINT country_pkey PRIMARY KEY (iso)

);

This file will be picked up by flyway on the next run of your spring app.

Create a JPA entity and a Spring Jpa Repository

Create a class named Country as below. The annotations of @Data is from the lombok project. That will generate the getters and setters for the class.


@Data
@Entity
public class Country {

    @Id
    private String iso;

    private String name;

    private String capital;
}

Create a simple Spring Jpa Repository as

public interface CountryRepository extends JpaRepository<Country,String> {}

The integration test

The integration test will run against Postgres 14.4. To enable the test to run using testcontainers I have added an application-ittest.properties file with the following content :

spring.datasource.url=jdbc:tc:postgresql:14.4://localhost:5432/tcdemo
spring.datasource.username=postgres
spring.datasource.password=postgres

Note that here the normal connection url of jdbc:postgresql//localhost:5432/tcdemo is modified to be jdbc:tc:postgresql:14.4://localhost:5432/tcdemo

I have created a junit 5 test which adds the Netherlands to the country table. The test runs with the profile ittest which means it will pick up the properties defined in the application-ittest.properties file.

@DataJpaTest
@ActiveProfiles("ittest")
@AutoConfigureTestDatabase(
        replace = AutoConfigureTestDatabase.Replace.NONE
)
public class CrudActionsOnCountryTableTest {

    @Autowired
    private CountryRepository countryRepository;

    @Test
    void shouldAddCountry() {

        Country country = new Country();
        country.setIso("NL");
        country.setName("The Netherlands");
        country.setCapital("Amsterdam");
        Country savedCountry = countryRepository.save(country);

        assertAll(
                () -> assertEquals("NL", savedCountry.getIso()),
                () -> assertEquals("The Netherlands", savedCountry.getName()),
                () -> assertEquals("Amsterdam", savedCountry.getCapital())
        );

    }
}

If you run this test you will see that testcontainers ensured the postgres image for version 14.4 is downloaded(if not already in your local docker repo) and the test uses this database. You can also see that the flyway script to create the database is also applied to this container database.

Of course Testcontainers is not limited to integration tests for Postgres. Use it for example with firing up an activemq, a rabbit mq or virtually anything that can be containerized.

Get the source

The source of this project is available on github