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.

8f2248c6bfcc6df39a2cd8edf4267cb5?s=128

Benjamin Muschko

December 13, 2018
Tweet

Transcript

  1. 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");
 }
  2. 15.

    Configuring Spring applica-on context static class Initializer implements↵ ApplicationContextInitializer<ConfigurableApplicationContext> {


    @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());
 }
 }
  3. 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());
 }
 }
  4. 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);
 }
 }
  5. 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);
 }
 }
  6. 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"
  7. 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();
 }