$30 off During Our Annual Pro Sale. View Details »

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
  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.