Integration Testing with

Testcontainers

 

  • Software Engineer @ AtomicJar
    • PhD Student @ RWTH Aachen
  • Coach & Trainer @ Styrascosoft
  • Testcontainers Maintainer and Open Source Enthusiast
  • Oracle Groundbreaker Ambassador

Kevin Wittek          @kiview

Why do we Test?

Key learnings

  • Easy setup of complete test environments
  • Not a lot effort to integration test using real external applications
  • Avoid integrated tests!
$ git clone https://github.com/foo/bar.git
$ cd bar
$ ./gradlew build

Pyramid of the Sun

Visoko, Bosnia

My definiton

For example:

  • Launch application server
  • Framework/Library interactions
  • Interact with external processes (e.g. over the network, file system, database) 

"Tests which interact with external systems/dependencies"

But why a pyramid?

Testing Honeycomb

Integration Testing transformation over the years

Why combine?

  • Easy setup of dev environment
  • Uniform build and test environments
    • Also self contained and portable!
  • No installation and setup of external software
    • Except Docker 😜
GenericContainer redis = 
    new GenericContainer(
      "redis:5.0.3-alpine"
    ).withExposedPorts(6379);

redis.start();

// test my stuff

redis.stop();
<dependency>
    <groupId>org.testcontainers</groupId>
    <artifactId>testcontainers</artifactId>
    <version>1.16.2</version>
    <scope>test</scope>
</dependency>

testImplementation "org.testcontainers:testcontainers:1.16.2"

The Project

  • testcontainers-java first released in 2015
  • 100% OSS, MIT licensed
  • 52 releases, 305 contributors
  • Core maintainers
    • Richard North
    • Sergei Egorov
    • Kevin Wittek
  • Forks in Python, C#, Rust, Go, JS; Scala wrapper (+ unofficial Clojure wrapper )

@whichrich

Capabilities

  • Generic docker container support
    • use any Docker image to support tests
  • Databases (many!) and Stream processing (Kafka, Pulsar)
  • AWS, Azure and GCloud mocks (e.g., Localstack)
  • Docker Compose
    • Or as a Docker Compose alternative!
  • Selenium
  • Chaos testing (toxiproxy)

Capabilities (2)

  • 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!)

Support all Major CI Systems!

  • Jenkins
  • TravisCI
  • GitlabCI
  • CircleCI
  • Azure DevOps Pipelines
  • Bitbucket
  • GitHub Actions
  • ...
@Rule
public GenericContainer redis = 
  new GenericContainer(
    "redis:5.0.3-alpine"
  ).withExposedPorts(6379);

JUnit4 Rules

Spock Extension


@Testcontainers
class TestContainersClassIT extends Specification {


  @Shared
  GenericContainer redis = 
    new GenericContainer("redis:5.0.3-alpine")
        .withExposedPorts(6379)
        
  // tests here
  
}

Testcontainers-Jupiter (JUnit5)

@Testcontainers
class SomeTest {

  @Container
  public GenericContainer redis = 
    new GenericContainer(
      "redis:5.0.3-alpine"
    ).withExposedPorts(6379);
    
  // test methods
}

DOs

  • Use Testcontainers
  • Use copyFileToContainer

DONT's

  • Use fixed host ports
  • Disable Ryuk without understanding the implications
  • Assume Podman is a Docker drop-in replacement

Singleton Container Pattern

abstract class AbstractContainerBaseTest {

    static final MySQLContainer MY_SQL_CONTAINER;

    static {
        MY_SQL_CONTAINER = new MySQLContainer();
        MY_SQL_CONTAINER.start();
    }
}

class FirstTest extends AbstractContainerBaseTest {

    @Test
    void someTestMethod() {
        String url = MY_SQL_CONTAINER.getJdbcUrl();

        // create a connection and run test as normal
    }
}

Testcontainers 1.16.1 

Testcontainers 1.16.2 

Docker-Compose


version: "3"

services:
  vote:
    image: vote-frontend
    command: python app.py
    volumes:
     - ./vote:/app
    ports:
      - "5000:80"

  redis:
    image: redis:alpine
    ports: ["6379"]

  worker:
    image: worker

  db:
    image: postgres:9.4

  result:
    image: result-frontend
    command: nodemon --debug server.js
    volumes:
      - ./result:/app
    ports:
      - "5001:80"
      - "5858:5858"

TC as a DockerCompose replacement

TDD workflows

  • Container startup introduces overhead
  • Testcontainers should be ephemeral
  • We still want to avoid test pollution and integrated tests

The Future?

  • New builder DSL
  • Container-Core
    • Test-Framework agnostic
    • OO abstraction of Docker containers
  • Other languages
    • More languages and aligning existing fork
  • Stay tuned for AtomicJar news!

Testcontainers APACOUC

By Kevin Wittek

Testcontainers APACOUC

  • 889