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. Restoring sanity to integra-on & func-onal tes-ng with TestContainers Benjamin

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

  3. Who writes integra-on tes-ng?

  4. 2 unit tests, 0 integra-on tests

  5. Reproducible environment

  6. Slow startup time

  7. Isolated & cross-platform

  8. There must be a better way!

  9. The obvious choice

  10. Java library for managing Docker container in JUnit tests

  11. Docker Engine communica-on

  12. Docker environment discovery

  13. Container cleanup

  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");
 }
  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());
 }
 }
  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());
 }
 }
  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);
 }
 }
  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);
 }
 }
  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"
  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();
 }
  21. Demo Time! hNps:/ /github.com/bmuschko/testcontainers-demo

  22. Thanks! Please ask ques-ons.