Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

Take Control of your Integration Testing with TestContainers

70 views

Published on

Slides from my Java2Days conference talk - Take Control of your Integration Testing with TestContainers.

How easy is it to write and maintain integration tests when your system under test interacts with databases, message stores, and other external systems? It can be quite challenging, can’t it? For example, the lack of control over the setup of databases can increase the cost of integration testing. Sometimes we take the route of using an in-memory database instead of the one we employ in the production environment, making the tests less effective. With Docker containers, you can simplify this to some extent by running the setup before invoking your tests. Imagine if you get to control the database environment right from within the test code. TestContainers bring in this convenience.

TestContainers is a tool that you can invoke from your test code. It provides lightweight, throwaway instances of common databases, web browsers(Selenium tests), or anything else that can run in a Docker container. In this presentation, I walk you through how TestContainers is the most effective way to take advantage of Docker containers for integration testing. I present through several use-cases and demonstrate how TestContainers simplifies integration testing.

Published in: Software
  • Be the first to comment

  • Be the first to like this

Take Control of your Integration Testing with TestContainers

  1. 1. Take Control of your Integration Testing with TestContainers Naresha K @naresha_k https://blog.nareshak.com/
  2. 2. About me Developer, Architect & Tech Excellence Coach Founder & Organiser Bangalore Groovy User Group
  3. 3. Integration Testing?
  4. 4. Infrastructure as Code
  5. 5. Infrastructure as Code
  6. 6. Infrastructure as Code Immutable Infrastructure
  7. 7. TestContainers
  8. 8. testImplementation 'org.junit.jupiter:junit-jupiter-api:5.6.2' testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.6.2' runtimeOnly 'mysql:mysql-connector-java:8.0.22' testImplementation "org.testcontainers:junit-jupiter:1.15.1" testCompile "org.testcontainers:mysql:1.15.1" build.gradle dependencies
  9. 9. @Testcontainers public class SampleMySqlTest { @Container private MySQLContainer mySQLContainer = new MySQLContainer(DockerImageName.parse("mysql:8")) .withDatabaseName("foo") .withUsername("foo") .withPassword("secret"); @Test void test1() { // invoke SUT && assert assertTrue(mySQLContainer.isRunning()); } @Test void test2() { // invoke SUT && assert assertTrue(mySQLContainer.isRunning()); } }
  10. 10. @Testcontainers public class SampleMySqlTest { @Container private MySQLContainer mySQLContainer = new MySQLContainer(DockerImageName.parse("mysql:8")) .withDatabaseName("foo") .withUsername("foo") .withPassword("secret"); @Test void test1() { // invoke SUT && assert assertTrue(mySQLContainer.isRunning()); } @Test void test2() { // invoke SUT && assert assertTrue(mySQLContainer.isRunning()); } } @Testcontainers @Container
  11. 11. @Testcontainers public class SampleMySqlTest { @Container private static MySQLContainer mySQLContainer = new MySQLContainer(DockerImageName.parse("mysql:8")) .withDatabaseName("foo") .withUsername("foo") .withPassword("secret"); @Test void test1() { // invoke SUT && assert assertTrue(mySQLContainer.isRunning()); } @Test void test2() { // invoke SUT && assert assertTrue(mySQLContainer.isRunning()); } }
  12. 12. @Testcontainers public class MySqlTest { @Container protected static final MySQLContainer mySQLContainer; static { mySQLContainer = new MySQLContainer(DockerImageName.parse("mysql:8")) .withDatabaseName("foo") .withUsername("foo") .withPassword("secret"); mySQLContainer.start(); } }
  13. 13. @Testcontainers public class MySqlTestSuite1 extends MySqlTest { @Test void test1() { assertTrue(mySQLContainer.isRunning()); } @Test void test2() { assertTrue(mySQLContainer.isRunning()); } }
  14. 14. Case Study #1
  15. 15. @Transactional class ConferenceService { def getConferencesInCities(List<String> cities) { Conference.where { city in cities }.list() } }
  16. 16. select this_.id as id1_0_0_, this_.version as version2_0_0_, this_.name as name3_0_0_, this_.city as city4_0_0_ from conference this_ where this_.city in ()
  17. 17. hibernate: cache: queries: false use_second_level_cache: false use_query_cache: false dataSource: pooled: true jmxExport: true driverClassName: org.h2.Driver username: sa password: '' logSql: true environments: development: dataSource: dbCreate: create-drop url: jdbc:h2:mem:devDb;MVCC=TRUE;LOCK_TIMEOUT=10000;DB_CLOSE_ON_EXIT=FALSE test: dataSource: dbCreate: update url: jdbc:h2:mem:testDb;MVCC=TRUE;LOCK_TIMEOUT=10000;DB_CLOSE_ON_EXIT=FALSE Application.yml (Test with H2)
  18. 18. dataSource: pooled: true jmxExport: true driverClassName: com.mysql.jdbc.Driver username: root password: secret logSql: true environments: development: dataSource: dbCreate: create-drop url: jdbc:h2:mem:devDb;MVCC=TRUE;LOCK_TIMEOUT=10000;DB_CLOSE_ON_EXIT=FALSE test: dataSource: dbCreate: update url: jdbc:mysql://localhost:3306/testapp Application.yml (Test with MySQL)
  19. 19. testCompile platform('org.testcontainers:testcontainers-bom:1.12.5') testCompile 'org.testcontainers:spock' testCompile 'org.testcontainers:mysql' build.gradle
  20. 20. dataSource: pooled: true jmxExport: true driverClassName: org.testcontainers.jdbc.ContainerDatabaseDriver logSql: true environments: test: dataSource: dbCreate: update url: jdbc:tc:mysql:8://somehostname:someport/databasename Application.yml (Test with TestContainers)
  21. 21. @Integration @Rollback @Testcontainers class ConferenceTestContainerSpec extends Specification { @Shared MySQLContainer mySQLContainer = new MySQLContainer( DockerImageName.parse("mysql:8")) .withDatabaseName("testapp") .withUsername("user") .withPassword("secret") ConferenceService conferenceService
  22. 22. void "should return empty list"() { given: List<String> cities = [] when: List<Conference> conferences = conferenceService.getConferencesInCities(cities) then: conferences.size() == 0 }
  23. 23. @Integration @Rollback @Testcontainers class ConferenceTestContainerSpec extends Specification { @Shared MySQLContainer mySQLContainer = new MySQLContainer() .withDatabaseName("testapp") .withUsername("user") .withPassword("secret") ConferenceService conferenceService void "should return empty list"() { // test code } } dataSource: driverClassName: org.testcontainers.jdbc.ContainerDatabaseDriver environments: test: dataSource: dbCreate: update url: jdbc:tc:mysql:8://somehostname:someport/databasename
  24. 24. Case Study #2
  25. 25. testCompile platform('org.testcontainers:testcontainers-bom:1.15.1') testCompile 'org.testcontainers:spock' testCompile 'org.testcontainers:mysql' testCompile "org.testcontainers:localstack" build.gradle
  26. 26. @Integration @Testcontainers class FileStorageSpec extends Specification { public static final String TARGET_BUCKET = "testbucket" AmazonS3Service amazonS3Service FileStorageService fileStorageService @Shared public LocalStackContainer localstack = new LocalStackContainer( DockerImageName.parse("localstack/localstack:0.11.2")) .withServices(S3)
  27. 27. void setup() { AmazonS3 s3 = AmazonS3ClientBuilder .standard() .withEndpointConfiguration( localstack.getEndpointConfiguration(S3)) .withCredentials( localstack.getDefaultCredentialsProvider()) .build(); amazonS3Service.client = s3 amazonS3Service.createBucket(TARGET_BUCKET) }
  28. 28. def "file can be stored in s3 storage and read"() { given: def filePath = Paths.get("src/test/resources/car.jpg") InputStream stream = Files.newInputStream(filePath) when: fileStorageService.storeFile(TARGET_BUCKET, "vehicles/123.jpg", stream) then: amazonS3Service.exists(TARGET_BUCKET, "vehicles/123.jpg") when: File file = fileStorageService.readFile(TARGET_BUCKET, "vehicles/123.jpg", Files.createTempFile(null, null).toAbsolutePath().toString()) then: file and: file.newInputStream().bytes == filePath.bytes }
  29. 29. https://www.testcontainers.org/modules/databases/
  30. 30. https://www.testcontainers.org/modules/localstack/
  31. 31. @Testcontainers public class GenericContainerTest { @Container private static GenericContainer<?> redis = new GenericContainer<>( DockerImageName.parse("redis:6")) .withExposedPorts(6379); @Test void testRedisOps() { assertTrue(redis.isRunning()); } }
  32. 32. @Testcontainers public class GenericContainerTest { @Container private static GenericContainer<?> redis = new GenericContainer<>( DockerImageName.parse("redis:6")) .withExposedPorts(6379); @Test void testRedisOps() { assertTrue(redis.isRunning()); } } //get host port redis.getMappedPort(6379)
  33. 33. @Container private static GenericContainer<?> tomcat = new GenericContainer<>( DockerImageName.parse("tomcat:8.5.8-jre8")) .withExposedPorts(8080);
  34. 34. @Container private static GenericContainer<?> tomcat = new GenericContainer<>( DockerImageName.parse("tomcat:8.5.8-jre8")) .withExposedPorts(8080) .withStartupTimeout(Duration.ofMinutes(2));
  35. 35. @Container private static GenericContainer<?> tomcat = new GenericContainer<>( DockerImageName.parse("tomcat:8.5.8-jre8")) .withExposedPorts(8080) .waitingFor(Wait.forHttp("/"));
  36. 36. @Container private static GenericContainer<?> tomcat = new GenericContainer<>( DockerImageName.parse("tomcat:8.5.8-jre8")) .withExposedPorts(8080) .waitingFor(Wait.forHttp("/") .forStatusCode(200)); https://www.testcontainers.org/features/startup_and_waits/
  37. 37. web: image: "tomcat:8.5.50-jdk8-adoptopenjdk-hotspot" ports: - "8080:8080" redis: image: "redis:6" docker-compose.yml
  38. 38. @Container private DockerComposeContainer<?> container = new DockerComposeContainer<>( new File("src/test/resources/docker-compose.yml")) .withExposedService("web_1", 8080) .withExposedService("redis_1", 6379);
  39. 39. Integration Tests are not replacements for Unit Tests
  40. 40. References https://github.com/naresha/java2days2020testcontainers https://www.testcontainers.org/ https://www2.slideshare.net/nareshak
  41. 41. Thank You

×