Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Restoring sanity to integration & functional testing with TestContainers

Restoring sanity to integration & functional testing with TestContainers

Many organizations struggle with maintaining tests that require more complex setup procedures. As a result, tests become flaky, unreliable and require manual intervention. In a world of "automate all the things" this is very counterproductive and unnecessarily costs organizations time and money.

In this lightning talk, we'll have a look at setting up and running integration & functional tests with the help of the open source library TestContainers. You will learn how to stand up lightweight, disposable Docker instances running your application as reliable test fixtures.

Benjamin Muschko

December 13, 2018
Tweet

More Decks by Benjamin Muschko

Other Decks in Programming

Transcript

  1. Restoring sanity to integra-on &
    func-onal tes-ng with TestContainers
    Benjamin Muschko

    View full-size slide

  2. AUTOMATED
    ASCENT
    bmuschko
    bmuschko
    bmuschko.com
    About the speaker
    automatedascent.com

    View full-size slide

  3. Who writes
    integra-on tes-ng?

    View full-size slide

  4. 2 unit tests, 0 integra-on tests

    View full-size slide

  5. Reproducible
    environment

    View full-size slide

  6. Slow startup
    time

    View full-size slide

  7. Isolated &
    cross-platform

    View full-size slide

  8. There must
    be a better
    way!

    View full-size slide


  9. The obvious choice

    View full-size slide

  10. Java library for managing
    Docker container in JUnit tests

    View full-size slide

  11. Docker Engine communica-on

    View full-size slide

  12. Docker environment discovery

    View full-size slide

  13. Container cleanup

    View full-size slide

  14. Crea-ng a database container
    @Testcontainers

    public class DatabaseIntegrationTest {


    @Container

    public static PostgreSQLContainer postgreSqlContainer =
    new PostgreSQLContainer("postgres:9.6.10-alpine")

    .withUsername("username")

    .withPassword("pwd")

    .withDatabaseName("todo");

    }

    View full-size slide

  15. Configuring Spring applica-on context
    static class Initializer implements↵
    ApplicationContextInitializer {

    @Override

    public void initialize(ConfigurableApplicationContext↵
    configurableApplicationContext) {

    TestPropertyValues.of(

    "spring.datasource.url=" + container.getJdbcUrl(),

    "spring.datasource.username=" + container.getUsername(),

    "spring.datasource.password=" + container.getPassword(),

    "spring.datasource.driver-class-name=org.postgresql.Driver",

    "spring.jpa.generate-ddl=true"

    ).applyTo(configurableApplicationContext.getEnvironment());

    }

    }

    View full-size slide

  16. Star-ng a generic container
    @Testcontainers

    public class ApplicationIntegrationTest {


    @Container

    public static GenericContainer appContainer = createAppContainer();


    private static GenericContainer createAppContainer() {

    return new GenericContainer(buildImageDockerfile())

    .withExposedPorts(8080)

    .withEnv("SPRING_PROFILES_ACTIVE", "dev")

    .waitingFor(Wait.forHttp("/actuator/health")

    .forStatusCode(200));

    }


    private static ImageFromDockerfile buildImageDockerfile() {

    return new ImageFromDockerfile()

    .withFileFromFile(ARCHIVE_NAME, new File(DISTRIBUTION_DIR, ARCHIVE_NAME))

    .withDockerfileFromBuilder(builder -> builder

    .from("openjdk:jre-alpine")

    .copy(ARCHIVE_NAME, "/app/" + ARCHIVE_NAME)

    .entryPoint("java", "-jar", "/app/" + ARCHIVE_NAME)

    .build());

    }

    }

    View full-size slide

  17. Accessing container IP & ports
    private URL buildEndpointUrl(String context) {

    StringBuilder url = new StringBuilder();

    url.append("http://");

    url.append(appContainer.getContainerIpAddress());

    url.append(":");

    url.append(appContainer.getFirstMappedPort());

    url.append(context);


    try {

    return new URL(url.toString());

    } catch (MalformedURLException e) {

    throw new RuntimeException("Invalid URL", e);

    }

    }

    View full-size slide

  18. Managing a set of services
    @Testcontainers

    public class DockerComposeIntegrationTest {


    private final static String POSTGRES_SERVICE_NAME = "database_1";

    private final static int POSTGRES_SERVICE_PORT = 5432;


    @Container

    public static DockerComposeContainer environment =↵
    createComposeContainer();


    private static DockerComposeContainer createComposeContainer() {

    return new DockerComposeContainer(new File(PROJECT_DIR,↵
    "src/test/resources/compose-test.yml"))

    .withExposedService(POSTGRES_SERVICE_NAME,
    POSTGRES_SERVICE_PORT);

    }

    }

    View full-size slide

  19. Sample Docker Compose file
    database:

    image: "postgres:9.6.10-alpine"

    environment:

    - POSTGRES_USER=postgres

    - POSTGRES_PASSWORD=postgres

    - POSTGRES_DB=todo

    elasticsearch:

    image: "elasticsearch"

    View full-size slide

  20. Retrieving service host & port
    private static String getPostgresServiceUrl() {

    String postgresHost =↵
    environment.getServiceHost(POSTGRES_SERVICE_NAME,↵
    POSTGRES_SERVICE_PORT);

    Integer postgresPort =↵
    environment.getServicePort(POSTGRES_SERVICE_NAME,↵
    POSTGRES_SERVICE_PORT);

    StringBuilder postgresServiceUrl = new StringBuilder();

    postgresServiceUrl.append("jdbc:postgresql://");

    postgresServiceUrl.append(postgresHost);

    postgresServiceUrl.append(":");

    postgresServiceUrl.append(postgresPort);

    postgresServiceUrl.append("/todo");

    return postgresServiceUrl.toString();

    }

    View full-size slide

  21. Demo Time!
    hNps:/
    /github.com/bmuschko/testcontainers-demo

    View full-size slide

  22. Thanks!
    Please ask ques-ons.

    View full-size slide