Slide 1

Slide 1 text

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

Slide 2

Slide 2 text

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

Slide 3

Slide 3 text

Who writes integra-on tes-ng?

Slide 4

Slide 4 text

2 unit tests, 0 integra-on tests

Slide 5

Slide 5 text

Reproducible environment

Slide 6

Slide 6 text

Slow startup time

Slide 7

Slide 7 text

Isolated & cross-platform

Slide 8

Slide 8 text

There must be a better way!

Slide 9

Slide 9 text

The obvious choice

Slide 10

Slide 10 text

Java library for managing Docker container in JUnit tests

Slide 11

Slide 11 text

Docker Engine communica-on

Slide 12

Slide 12 text

Docker environment discovery

Slide 13

Slide 13 text

Container cleanup

Slide 14

Slide 14 text

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");
 }

Slide 15

Slide 15 text

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());
 }
 }

Slide 16

Slide 16 text

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());
 }
 }

Slide 17

Slide 17 text

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);
 }
 }

Slide 18

Slide 18 text

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);
 }
 }

Slide 19

Slide 19 text

Sample Docker Compose file database:
 image: "postgres:9.6.10-alpine"
 environment:
 - POSTGRES_USER=postgres
 - POSTGRES_PASSWORD=postgres
 - POSTGRES_DB=todo
 elasticsearch:
 image: "elasticsearch"

Slide 20

Slide 20 text

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();
 }

Slide 21

Slide 21 text

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

Slide 22

Slide 22 text

Thanks! Please ask ques-ons.