Intro to

Testcontainers

 

Key learnings

  • Easy setup of complete test environments
  • Not a lot effort to integration test using real external applications
  • Development history of Docker Spock-Extension
$ git clone https://github.com/foo/bar.git
$ cd bar
$ ./gradlew build

How to fuse both worlds?

Spock + Docker = Spocker?

Or: How to build your own Spock extension

@Docker(image = "emilevauge/whoami", ports = @PortBinding("8080:80"))
class DockerExtensionIT extends Specification {

    def "should start accessible docker container"() {
        given: "a http client"
        def client = HttpClientBuilder.create().build()

        when: "accessing web server"
        def response = client.execute(new HttpGet("http://localhost:8080"))

        then: "docker container is running and returns http status code 200"
        response.statusLine.statusCode == 200
    }

}

First commit

class DockerMethodInterceptor extends AbstractMethodInterceptor {

    private final DockerClientFacade dockerClient

    DockerMethodInterceptor(Docker docker) {
        this(new DockerClientFacade(docker))
    }

    DockerMethodInterceptor(DockerClientFacade dockerClient) {
        this.dockerClient = dockerClient
    }

    @Override
    void interceptSpecExecution(IMethodInvocation invocation) throws Throwable {
        dockerClient.startContainer()
        invocation.proceed()
        dockerClient.stopContainer()
    }
}

First commit

GenericContainer redis =
    new GenericContainer("redis:3.0.2")
               .withExposedPorts(6379);

redis.start();

// test my stuff

redis.stop();
  • Generic Containers
  • Specialized Containers (Databases, Selenium, Kafka)
  • Dockerfile 
  • Dockerfile DSL
  • Docker-Compose
  • Dynamic port binding and API
  • WaitStrategies
  • Docker environment discovery (e.g. docker-machine, DOCKER_HOST, Docker for Mac, Docker for Windows)
  • Platform independent
    • Linux, macOS, Windows 10 (with NPIPE support!)

@ashleymcnamara

Works on Windows too!

Testcontainers 1.9.1

Testcontainers 1.10.1

JDK11 Compatible!

Annotations a good idea?

@Docker(image = "postgres", ports = ["5432:5432"], env = [
        @Env(key = "POSTGRES_USER", value = "foo"),
        @Env(key = "POSTGRES_PASSWORD", value = "secret")
        ], 
        waitStrategy = { new JdbcWaitStrategy(username: "foo", 
            password: "secret", 
            jdbcUrl: "jdbc:postgresql:foo", 
            driverClassName: "org.postgresql.Driver", 
            testQueryString: "SELECT 1") })
def "waits until postgres accepts jdbc connections"() {
    // tests
}

Testcontainers all in!


@Testcontainers
class TestContainersClassIT extends Specification {


    @Shared
    GenericContainer genericContainer = new GenericContainer("postgres:latest")
            .withExposedPorts(5432)
            .withEnv([
                POSTGRES_USER: "foo"
                POSTGRES_PASSWORD: "secret"
            ])
}
// Set up a redis container
@ClassRule
public static GenericContainer redis =
    new GenericContainer("redis:3.0.2")
               .withExposedPorts(6379);

Testcontainers-Jupiter (JUnit5)

@Testcontainers
class SomeTest {

    @Container
    private MySQLContainer mySQLContainer = new MySQLContainer();

    @Test
    void someTestMethod() {
        String url = mySQLContainer.getJdbcUrl();
         // create a connection and run test as normal
    }
}

CI inside containers?

Docker in Docker - DinD

Docker Wormhole

Docker Wormhole

Future

  • Testcontainers 2.0
  • Container-Core
    • Test-Framework agnostic
    • OO abstraction of Docker containers
  • Other languages
    • .NET, Go, Rust and JavaScript already exist (moved to Testcontainers org on Github)
  • Windows Containers on Windows (WCOW) support
  • Build and test inside containers with full IDE support

Resources

Intro to Testcontainers

By Kevin Wittek

Intro to Testcontainers

  • 1,366