Paulo's Blog

Introduction to Testcontainers

June 22, 2020

Testcontainers is a tool that easily supports adding Docker containers in the context of your JUnit tests. It manages the container lifecycle for you like creating and destroying containers and making sure that they are ready to receive connection before you start testing.

Let’s see how you integrate Testcontainers on a Spring Boot project and explore a few use cases using Postgres.

Create the Spring Boot Project

You can do on your IDE and in any means you like. For this we’re using https://start.spring.io/ as it doesn’t depend on any external tooling.

Your language and build preference doesn’t matter here. I’ll be using Kotlin and Gradle just as a personal preference. Chose the latest release version of Spring Boot (2.6 by the time of this writing) and add Spring Data JPA as a dependency. Generate your project and we’re set.

Setting up the Application

Now before using Testcontainers in our project we need to add the code to connect to the database and query for data. Let’s create a users table and the code to query for a user. Create a User.kt file containing:

package com.example.demo

import javax.persistence.Entity
import javax.persistence.Id
import javax.persistence.Table

@Entity
@Table(name = "users")
data class User(
        @Id
        val id: Int,
        val firstName: String,
        val lastName: String
)

And a UserRepository.kt

package com.example.demo

import org.springframework.data.jpa.repository.JpaRepository
import org.springframework.stereotype.Repository

@Repository
interface UserRepository : JpaRepository<User, Int>

Now edit application.properties and add the following:

# Database connection information
spring.datasource.url=jdbc:postgresql://localhost:5432/test
spring.datasource.username=postgres
spring.datasource.password=postgres
spring.jpa.properties.hibernate.dialect = org.hibernate.dialect.PostgreSQLDialect

# Creates the database schema when the application starts
spring.jpa.hibernate.ddl-auto=create

Testcontainers Setup

Edit your build.gradle and add the Testcontainers dependency to your project.

testImplementation("org.testcontainers:junit-jupiter:1.14.0") `testImplementation(“org.testcontainers:postgresql:1.14.0”)

We’re going to use a base class for tests that access the database. On your test package create the class DatabaseTest.kt:

package com.example.demo

import org.springframework.boot.test.context.SpringBootTest
import org.springframework.boot.test.util.TestPropertyValues
import org.springframework.context.ApplicationContextInitializer
import org.springframework.context.ConfigurableApplicationContext
import org.springframework.test.context.ContextConfiguration
import org.testcontainers.containers.PostgreSQLContainer
import org.testcontainers.junit.jupiter.Container
import org.testcontainers.junit.jupiter.Testcontainers

class KPostgresContainer: PostgreSQLContainer<KPostgresContainer>()

@Testcontainers
@SpringBootTest
@ContextConfiguration(initializers = [DatabaseTest.Initializer::class])
class DatabaseTest {

    companion object {
        @Container
        val container = KPostgresContainer()
                .withExposedPorts(5432)
                .withDatabaseName("test")
                .withUsername("postgres")
                .withPassword("postgres")
    }

    class Initializer : ApplicationContextInitializer<ConfigurableApplicationContext> {
        override fun initialize(applicationContext: ConfigurableApplicationContext) {
            TestPropertyValues.of(
                    "spring.datasource.url=jdbc:postgresql://localhost:${container.firstMappedPort}/test"
            ).applyTo(applicationContext.environment)
        }
    }

}

This class is the meat of the Testcontainers configuration. The first thing to notice is the class KPostgresContainer. That is a workaround necessary only when using Kotlin, due to the Java implementation of PostgreSQLContainer requiring a self reference.

Next we create the container using a static value annotated with the @Container classrule. By using this rule the creation and deletion of the containers is integrated with JUnit to make sure that the container is running before the tests are started. And it also assures that the container will be deleted properly after the tests are executed.

Then we define an Initializer so we can get container information and inject in our Spring environment. This is necessary because Testcontainers exposes the container in a random available port. And since your application is expecting the port 5432 as defined in application.yaml the test would fail as the application would not be able to connect to that port. By using the Initalizer we can inject that exposed port before the tests start.

Creating the Test

Now we can create the test. Let’s create a simple test that checks if a value has been inserted in the database. Create the class UserRepositoryTest.kt:

package com.example.demo

import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Test
import org.springframework.beans.factory.annotation.Autowired

class UserRepositoryTest : DatabaseTest() {

    @Autowired
    lateinit var userRepository: UserRepository

    @Test
    fun `test can insert user`() {
        val user = User(1, "Paulo", "Costa")
        userRepository.save(user)
        assertEquals(user, userRepository.findById(1).get())
    }

}

If everything went well the test should pass and you have your environment ready to use Testcontainers. You can check the code for this post here. Just clone and checkout the simple-test tag.

In the Next Series We’ll look at using init scripts to add test data before our tests are executed.


© 2021, blog.paulocosta.dev