Categories
Java

Spring Boot Unit and Integration Test With Testcontainers

Testcontainers is a very elegant, clean library to write Unit and Integration tests using docker containers, which provides almost real stacks for testing Java Spring applications.

JUnit and Integration tests are some of the most important parts of CI/CD. Well-written tests with more and more code coverage can detect bugs early and help to improve the quality of the product.

Although Java and Spring have many open source libraries and frameworks for testing code using mocking techniques or embedded versions of middleware and software, and those are used very widely.

This testing can be improved further by using actual software and middleware. For this Testcontainers provides a very elegant, clean library to write Unit and Integration tests using docker containers. Testcontainers provide specialized modules for almost all databases, NoSql, and other components. It also provides a GenericContainer component in which can pull and run any container to test your code.

You can set up Docker on your local development PC by installing docker or you can also use remote docker to test your code.

In the case of Local installation, the library will automatically connect to local docker using the following strategies in order:

Environment variables:

  • DOCKER_HOST
  • DOCKER_TLS_VERIFY
  • DOCKER_CERT_PATH

Defaults Values

  • DOCKER_HOST=https://localhost:2376
  • DOCKER_TLS_VERIFY=1
  • DOCKER_CERT_PATH=~/.docker

If Docker Machine is installed, the docker-machine environment for the first machine is found. Docker Machine needs to be on the PATH for this to succeed.

Setting up Docker Service with TCP socket in Linux for Remote connection

Use the following steps to configure the docker service to listen on TCP port 2375

Create daemon.json file in /etc/docker directory and add following

{"hosts": ["tcp://0.0.0.0:2375", "unix:///var/run/docker.sock"]}

Create the following file with the text

[Service]
ExecStart=
ExecStart=/usr/bin/dockerd
/etc/systemd/system/docker.service.d/override.conf

Reload the systemd daemon:

systemctl daemon-reload

Restart the Docker service

systemctl restart docker.service

Now your docker service is ready to connect from a remote client, don’t forget to open port 2375.

Now on the development PC create environment variable

DOCKER_HOST with the value tcp://{0.0.0.0}:2375, replace 0.0.0.0 with actual docker server IP.

Now Testcontainers will connect to the remote Docker machine.

Testcontainers Basic Configuration in Spring Boot Test

To use Testcontainers in Spring boot application for Junit and Integration testing. First, you need to add the required test dependencies as given below, these are basic dependencies to use Testcontainers in spring boot.

testImplementation 'org.springframework.boot:spring-boot-starter-test:2.5.5'
    testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.1'
    testImplementation 'org.junit.jupiter:junit-jupiter-params:5.8.1'
    testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.8.1'
    testImplementation 'org.junit.platform:junit-platform-commons:1.8.1'
    testImplementation 'org.testcontainers:junit-jupiter:1.16.2'
    testImplementation
    testAnnotationProcessor 'org.projectlombok:lombok:1.18.22'

Apart from this, you need to add dependencies for the software you need to test. You can refer Modules section of the https://www.testcontainers.org/, which has almost every docker module like specialized images and generic versions of images.

Example of MySql Testcontainers test

To add MySQL use the following Gradle dependency.

testImplementation 'org.testcontainers:mysql:1.16.2'

Define all Spring Datasource properties using java code as given below using @DynamicPropertySource. Static Datasource properties you can keep in the application.properties or use as per your code style.

@SpringBootTest(classes = TokenServiceApplication.class,
        webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@AutoConfigureMockMvc
@Testcontainers
public abstract class AbstractIntegrationTest {
    @Autowired
    MockMvc mockMvc;

    @Container
    private static MySQLContainer database = new MySQLContainer("mysql");

    @DynamicPropertySource
    static void databaseProperties(DynamicPropertyRegistry registry) {
        registry.add("spring.datasource.url",database::getJdbcUrl);
        registry.add("spring.datasource.username", database::getUsername);
        registry.add("spring.datasource.password", database::getPassword);

    }
}

Example of Cassandra and Memcached Testcontainers test

Use the following Gradle dependencies. For redis, we will use a generic container.

testImplementation 'org.testcontainers:cassandra:1.16.2'

Define all Spring Datasource properties using java code as given below using @DynamicPropertySource.

@SpringBootTest(classes = RedirectServiceApplication.class,
        webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@AutoConfigureMockMvc
@Testcontainers
public abstract class AbstractIntegrationTest {
    @Autowired
    MockMvc mockMvc;

    @Container
    public static final CassandraContainer cassandra
            = (CassandraContainer) new CassandraContainer("cassandra:3.11.2").withExposedPorts(9042);

    @Container
    public static final GenericContainer redis
            = (GenericContainer) new GenericContainer("redis:3.0.6").withExposedPorts(6379);

    static ClientAndServer mockServer;

    @DynamicPropertySource
    static void setupCassandraConnectionProperties(DynamicPropertyRegistry registry) {
        registry.add("spring.data.cassandra.keyspace-name", "shorturldb"::toString);
        registry.add("spring.data.cassandra.contact-points", cassandra::getContainerIpAddress);
        registry.add("spring.data.cassandra.port", String.valueOf(cassandra.getMappedPort(9042))::toString);
        registry.add("spring.data.cassandra.local-datacenter", "datacenter1"::toString);
        registry.add("spring.data.cassandra.schema-action", "create_if_not_exists"::toString);

        createKeyspace(cassandra.getCluster());

        mockServer = startClientAndServer(8089);
        createExpectationForGetToken();

        registry.add("spring.redis.database", "0"::toString);
        registry.add("spring.redis.host", redis::getContainerIpAddress);
        registry.add("spring.redis.port", String.valueOf(redis.getMappedPort(6379))::toString);
        registry.add("spring.redis.timeout", "60000"::toString);

        redis.start();

    }

    @AfterAll
    public static void stopServer() {
        mockServer.stop();
        redis.start();
    }
    static void createKeyspace(Cluster cluster) {
        try(Session session = cluster.connect()) {
            session.execute("CREATE KEYSPACE IF NOT EXISTS shorturldb WITH replication = \n" +
                    "{'class':'SimpleStrategy','replication_factor':'1'};");
        }
    }

}

You can refer following Github repository for working examples https://github.com/sheelprabhakar/url-shortener-msa.

Leave a Reply

Your email address will not be published. Required fields are marked *

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