Slide 1

Slide 1 text

Testcontainers:
 a year-in-review Sergei Egorov Kraków, 15-17 May 2019 @bsideup

Slide 2

Slide 2 text

Top ques!ons (0) Room 11 Join at slido.com #geecon2019 Join at #geecon2019 Room 11 slido com Ask questions at
 slido.com
 #geecon2019
 room 1

Slide 3

Slide 3 text

Tweet photos
 #geecon
 @bsideup

Slide 4

Slide 4 text

About me • Testcontainers co-maintainer • Staff Engineer at Pivotal’s Spring R&D, working on Project Reactor ⚛ • Berlin Spring User Group co-organizer • Developer tools geek @bsideup

Slide 5

Slide 5 text

Integration testing Why it matters?

Slide 6

Slide 6 text

@bsideup

Slide 7

Slide 7 text

@bsideup

Slide 8

Slide 8 text

Integration testing Real-world, but isolated testing Spot the issues before the real environment Can be run during the development You have to start real databases Should be cross-platform Slower than Unit testing Pros Cons @bsideup

Slide 9

Slide 9 text

Integration testing transformation @bsideup

Slide 10

Slide 10 text

Mocking Integration testing transformation @bsideup

Slide 11

Slide 11 text

Mocking Local DBs Integration testing transformation @bsideup

Slide 12

Slide 12 text

Mocking Local DBs VMs
 (Vagrant) Integration testing transformation @bsideup

Slide 13

Slide 13 text

Mocking Local DBs VMs
 (Vagrant) Docker Integration testing transformation @bsideup

Slide 14

Slide 14 text

Mocking Local DBs VMs
 (Vagrant) Docker Fig
 (aka Docker Compose) Integration testing transformation @bsideup

Slide 15

Slide 15 text

Mocking Local DBs VMs
 (Vagrant) Docker Fig
 (aka Docker Compose) Docker API Integration testing transformation @bsideup

Slide 16

Slide 16 text

No content

Slide 17

Slide 17 text

Abstraction layer

Slide 18

Slide 18 text

CI friendly

Slide 19

Slide 19 text

Cross-platform

Slide 20

Slide 20 text

Docker Compose FTW! redis:
 image: redis
 ports:
 - "6379:6379"
 postgres:
 image: postgres
 ports:
 - "5432:5432"
 elasticsearch:
 image: elasticsearch:5.0.0
 ports:
 - "9200:9200"
 @bsideup

Slide 21

Slide 21 text

But…

Slide 22

Slide 22 text

Declarative YAML redis:
 image: redis
 ports:
 - "6379:6379"
 postgres:
 image: postgres
 ports:
 - "5432:5432"
 elasticsearch:
 image: elasticsearch:5.0.0
 ports:
 - "9200:9200"
 @bsideup

Slide 23

Slide 23 text

ports randomization? redis:
 image: redis
 ports:
 - "6379:6379"
 postgres:
 image: postgres
 ports:
 - "5432:5432"
 elasticsearch:
 image: elasticsearch:5.0.0
 ports:
 - "9200:9200"
 @bsideup

Slide 24

Slide 24 text

Container per test? redis:
 image: redis
 ports:
 - "6379:6379"
 postgres:
 image: postgres
 ports:
 - "5432:5432"
 elasticsearch:
 image: elasticsearch:5.0.0
 ports:
 - "9200:9200"
 @bsideup

Slide 25

Slide 25 text

IDE integration? @bsideup

Slide 26

Slide 26 text

Fighting with Docker environment

Slide 27

Slide 27 text

There is no place like

Slide 28

Slide 28 text

There is no place like … unless there is

Slide 29

Slide 29 text

Can we improve that?

Slide 30

Slide 30 text

No content

Slide 31

Slide 31 text

Testcontainers • http://github.com/testcontainers/testcontainers-java • Wraps docker-java library • Docker environment discovery (Win, Mac, Linux) • Containers cleanup on JVM shutdown @bsideup

Slide 32

Slide 32 text

As simple as PostgreSQLContainer postgresql = new PostgreSQLContainer()
 
 GenericContainer redis = new GenericContainer("redis:3")
 .withExposedPorts(6379) @bsideup

Slide 33

Slide 33 text

Users

Slide 34

Slide 34 text

@bsideup

Slide 35

Slide 35 text

Demo

Slide 36

Slide 36 text

1.6.x Jan, 2018 @bsideup

Slide 37

Slide 37 text

1.6.x • Kafka module Jan, 2018 @bsideup try (KafkaContainer kafka = new KafkaContainer()) { kafka.start(); testKafkaFunctionality(kafka.getBootstrapServers()); }

Slide 38

Slide 38 text

1.6.x • Kafka module • “Ryuk” Jan, 2018 @bsideup

Slide 39

Slide 39 text

1.7.x Apr, 2018 @bsideup

Slide 40

Slide 40 text

1.7.x • Maven BOM Apr, 2018 @bsideup org.testcontainers testcontainers-bom 1.11.2 bom import

Slide 41

Slide 41 text

1.7.x • Maven BOM • DockerCompose
 wait strategies Apr, 2018 @bsideup new DockerComposeContainer(new File("docker-compose.yml")) .withExposedService( "redis_1", REDIS_PORT, Wait.forListeningPort() ) .withExposedService( "db_1", 3306, Wait.forLogMessage(".*ready for connections.*\\s", 1) );

Slide 42

Slide 42 text

1.7.x • Maven BOM • DockerCompose
 wait strategies • Daemon threads Apr, 2018 @bsideup kiraThread.setDaemon(true); kiraThread.start();

Slide 43

Slide 43 text

1.7.x • Maven BOM • DockerCompose
 wait strategies • Daemon threads • MockServer module Apr, 2018 @bsideup try (MockServerContainer mockServer = new MockServerContainer()) { mockServer.start(); String expectedBody = "Hello Default World!"; MockServerClient client = new MockServerClient( mockServer.getContainerIpAddress(), mockServer.getServerPort() ); client.when(request("/hello")).respond(response(expectedBody)); // ... }

Slide 44

Slide 44 text

1.8.x Jun, 2018 @bsideup

Slide 45

Slide 45 text

1.8.x • OkHttp transport Jun, 2018 @bsideup

Slide 46

Slide 46 text

1.8.x • OkHttp transport • Test framework
 agnostic Jun, 2018 @bsideup public interface Startable extends AutoCloseable { void start(); void stop(); } public interface TestLifecycleAware { default void beforeTest(TestDescription description) {} default void afterTest( TestDescription description, Optional throwable ) {} }

Slide 47

Slide 47 text

1.8.x • OkHttp transport • Test framework
 agnostic • Docker cred. helpers Jun, 2018 @bsideup { "auths": { }, "HttpHeaders": { "User-Agent": "Docker-Client/18.03.0-ce (darwin)" }, "credHelpers": { "registry.example.com": “helper" } }

Slide 48

Slide 48 text

1.8.x • OkHttp transport • Test framework
 agnostic • Docker cred. helpers • copyFileToContainer Jun, 2018 @bsideup GenericContainer container = new GenericContainer("alpine:latest") // Look, Ma! No volumes mounting! .withCopyFileToContainer( MountableFile.forClasspathResource(“/mappable-resource/"), containerPath )

Slide 49

Slide 49 text

1.8.x • OkHttp transport • Test framework
 agnostic • Docker cred. helpers • copyFileToContainer • Pulsar module • Couchbase module • Cassandra module Jun, 2018 @bsideup

Slide 50

Slide 50 text

1.9.x Sep, 2018 @bsideup

Slide 51

Slide 51 text

1.9.x • OkHttp by default Sep, 2018 @bsideup

Slide 52

Slide 52 text

1.9.x • OkHttp by default • Windows npipe support Sep, 2018 @bsideup No longer needed

Slide 53

Slide 53 text

1.9.x • OkHttp by default • Windows npipe support • Registry auth on Windows Sep, 2018 @bsideup

Slide 54

Slide 54 text

1.9.x • OkHttp by default • Windows npipe support • Registry auth on Windows • Fix local Docker Compose
 on Windows Sep, 2018 @bsideup new DockerComposeContainer(new File("docker-compose.yml")) .withExposedService("redis_1", REDIS_PORT) .withExposedService("db_1", 3306) .withLocalCompose(true);

Slide 55

Slide 55 text

1.9.x • OkHttp by default • Windows npipe support • Registry auth on Windows • Fix local Docker Compose
 on Windows • Host ports exposing Sep, 2018 @bsideup @BeforeClass public static void setUp() { localPort = server.getAddress().getPort(); Testcontainers.exposeHostPorts(localPort); } @Rule public BrowserWebDriverContainer browser = new BrowserWebDriverContainer() .withCapabilities(new ChromeOptions()); @Test public void testContainerRunningAgainstExposedHostPort() { RemoteWebDriver webDriver = browser.getWebDriver(); webDriver.get( String.format("http://host.testcontainers.internal:%d/", localPort)); final String pageSource = webDriver.getPageSource(); assertTrue(pageSource.contains("Hello from the host!")); }

Slide 56

Slide 56 text

1.9.x • OkHttp by default • Windows npipe support • Registry auth on Windows • Fix local Docker Compose
 on Windows • Host ports exposing • Random ports in Couchbase Sep, 2018 @bsideup

Slide 57

Slide 57 text

1.9.x • OkHttp by default • Windows npipe support • Registry auth on Windows • Fix local Docker Compose
 on Windows • Host ports exposing • Random ports in Couchbase Sep, 2018 @bsideup

Slide 58

Slide 58 text

1.9.x • OkHttp by default • Windows npipe support • Registry auth on Windows • Fix local Docker Compose
 on Windows • Host ports exposing • Random ports in Couchbase • ClickHouse and Postgis modules Sep, 2018 @bsideup

Slide 59

Slide 59 text

1.10.x Nov, 2018 @bsideup

Slide 60

Slide 60 text

1.10.x • JUnit 5 support Nov, 2018 @bsideup @Testcontainers class MyTestcontainersTests { // will be shared between test methods @Container static final MySQLContainer MYSQL_CONTAINER = new MySQLContainer(); // will be started before and stopped after each test method @Container PostgreSQLContainer postgresqlContainer = new PostgreSQLContainer() .withDatabaseName("foo") .withUsername("foo") .withPassword(“secret"); @Test void test() { assertTrue(MYSQL_CONTAINER.isRunning()); assertTrue(postgresqlContainer.isRunning()); } }

Slide 61

Slide 61 text

1.10.x • JUnit 5 support • New docs & more examples Nov, 2018 @bsideup

Slide 62

Slide 62 text

1.10.x • JUnit 5 support • New docs & more examples • Env var to turn off Ryuk
 (for public CIs) Nov, 2018 @bsideup

Slide 63

Slide 63 text

1.10.x • JUnit 5 support • New docs & more examples • Env var to turn off Ryuk
 (for public CIs) • shm + TmpFS settings Nov, 2018 @bsideup

Slide 64

Slide 64 text

1.10.x • JUnit 5 support • New docs & more examples • Env var to turn off Ryuk
 (for public CIs) • shm + TmpFS settings • Auto dependency updates
 with Dependabot Nov, 2018 @bsideup

Slide 65

Slide 65 text

1.10.x • JUnit 5 support • New docs & more examples • Env var to turn off Ryuk
 (for public CIs) • shm + TmpFS settings • Auto dependency updates
 with Dependabot Nov, 2018 @bsideup

Slide 66

Slide 66 text

1.10.x • JUnit 5 support • New docs & more examples • Env var to turn off Ryuk
 (for public CIs) • shm + TmpFS settings • Auto dependency updates
 with Dependabot • Neo4j and Elasticsearch modules Nov, 2018 @bsideup

Slide 67

Slide 67 text

1.11.x Mar, 2019 @bsideup

Slide 68

Slide 68 text

1.11.x • Chaos Testing (toxiproxy) Mar, 2019 @bsideup @Rule public GenericContainer redis = new GenericContainer("redis:5.0.4") .withExposedPorts(6379) .withNetwork(network); @Rule public ToxiproxyContainer toxiproxy = new ToxiproxyContainer() .withNetwork(network); @Test public void testLatencyViaProxy() throws IOException { ContainerProxy proxy = toxiproxy.getProxy(redis, 6379); Jedis jedis = new Jedis( proxy.getContainerIpAddress(), proxy.getProxyPort() ); proxy.toxics() .latency("latency", ToxicDirection.DOWNSTREAM, 1_100) .setJitter(100); jedis.get("somekey"); }

Slide 69

Slide 69 text

1.11.x • Chaos Testing (toxiproxy) • fsync=off for PostgreSQL Mar, 2019 @bsideup

Slide 70

Slide 70 text

1.11.x • Chaos Testing (toxiproxy) • fsync=off for PostgreSQL • Drop Netty transport Mar, 2019 @bsideup

Slide 71

Slide 71 text

1.11.x • Chaos Testing (toxiproxy) • fsync=off for PostgreSQL • Drop Netty transport Mar, 2019 @bsideup

Slide 72

Slide 72 text

1.11.x • Chaos Testing (toxiproxy) • fsync=off for PostgreSQL • Drop Netty transport • Rework shading Mar, 2019 @bsideup

Slide 73

Slide 73 text

Roadmap

Slide 74

Slide 74 text

new DSL

Slide 75

Slide 75 text

What’s wrong with the current one?

Slide 76

Slide 76 text

KafkaContainer kafka = new KafkaContainer() .withLogConsumer(new Slf4jLogConsumer(log)) .withEmbeddedZookeeper() .withEnv("FOO", "BAR") .withStartupAttempts(5); Current DSL @bsideup

Slide 77

Slide 77 text

KafkaContainer kafka = new KafkaContainer() .withLogConsumer(new Slf4jLogConsumer(log)) .withEmbeddedZookeeper() .withEnv("FOO", "BAR") .withStartupAttempts(5); Current DSL GenericContainer @bsideup

Slide 78

Slide 78 text

KafkaContainer kafka = new KafkaContainer() .withLogConsumer(new Slf4jLogConsumer(log)) .withEmbeddedZookeeper() .withEnv("FOO", "BAR") .withStartupAttempts(5); Current DSL KafkaContainer @bsideup

Slide 79

Slide 79 text

KafkaContainer kafka = new KafkaContainer() .withLogConsumer(new Slf4jLogConsumer(log)) .withEmbeddedZookeeper() .withEnv("FOO", "BAR") .withStartupAttempts(5); Current DSL GenericContainer @bsideup

Slide 80

Slide 80 text

KafkaContainer kafka = new KafkaContainer() .withLogConsumer(new Slf4jLogConsumer(log)) .withEmbeddedZookeeper() .withEnv("FOO", "BAR") .withStartupAttempts(5); Current DSL GenericContainer @bsideup

Slide 81

Slide 81 text

KafkaContainer kafka = new KafkaContainer() .withLogConsumer(new Slf4jLogConsumer(log)) .withEmbeddedZookeeper() .withEnv("FOO", "BAR") .withStartupAttempts(5); Current DSL GenericContainer public SELF withStartupAttempts(int attempts) { this.startupAttempts = attempts; return self(); } @bsideup

Slide 82

Slide 82 text

public class KafkaContainer extends GenericContainer {} public class GenericContainer> /* */ {} @bsideup

Slide 83

Slide 83 text

public class KafkaContainer extends GenericContainer {} public class GenericContainer> /* */ {} https://youtrack.jetbrains.com/issue/KT-17186 https://stackoverflow.com/questions/39163749/how-to-use-a-java-self-bounded-class-in-scala @bsideup

Slide 84

Slide 84 text

❌ Hard to maintain @bsideup

Slide 85

Slide 85 text

❌ Hard to maintain ❌ Does not work with Kotlin/Scala @bsideup

Slide 86

Slide 86 text

❌ Hard to maintain ❌ Does not work with Kotlin/Scala ❌ Externally mutable objects @bsideup

Slide 87

Slide 87 text

❌ Hard to maintain ❌ Does not work with Kotlin/Scala ❌ Externally mutable objects ❌ “setX” isn’t supported, only “withX” (think collections) @bsideup

Slide 88

Slide 88 text

❌ Hard to maintain ❌ Does not work with Kotlin/Scala ❌ Externally mutable objects ❌ “setX” isn’t supported, only “withX” (think collections) ❌ No imperative “if-else” with the fluent style @bsideup

Slide 89

Slide 89 text

KafkaContainer kafka = new KafkaContainer() { @Override protected void initialize() { withLogConsumer(new Slf4jLogConsumer(log)); withEmbeddedZookeeper(); withEnv("FOO", "BAR"); withStartupAttempts(5); } }; New “DSL”? @bsideup

Slide 90

Slide 90 text

KafkaContainer kafka = new KafkaContainer() { @Override protected void initialize() { withLogConsumer(new Slf4jLogConsumer(log)); withEmbeddedZookeeper(); withEnv("FOO", "BAR"); withStartupAttempts(5); } }; New “DSL”? Or even… @bsideup

Slide 91

Slide 91 text

KafkaContainer kafka = new KafkaContainer() {{ withLogConsumer(new Slf4jLogConsumer(log)); withEmbeddedZookeeper(); withEnv("FOO", "BAR"); withStartupAttempts(5); }} New “DSL”? @bsideup

Slide 92

Slide 92 text

KafkaContainer kafka = new KafkaContainer() {{ withLogConsumer(new Slf4jLogConsumer(log)); withEmbeddedZookeeper(); withEnv("FOO", "BAR"); withStartupAttempts(5); }} New “DSL”? public void withStartupAttempts(int attempts) { this.startupAttempts = attempts; } @bsideup

Slide 93

Slide 93 text

KafkaContainer kafka = new KafkaContainer() {{ withLogConsumer(new Slf4jLogConsumer(log)); withEmbeddedZookeeper(); withEnv("FOO", "BAR"); withStartupAttempts(5); }} New “DSL”? public void withStartupAttempts(int attempts) { this.startupAttempts = attempts; } @bsideup

Slide 94

Slide 94 text

✓ Super easy to maintain @bsideup

Slide 95

Slide 95 text

✓ Super easy to maintain ✓ Works with any JVM language @bsideup

Slide 96

Slide 96 text

✓ Super easy to maintain ✓ Works with any JVM language ✓ “Controllable mutability” - no modifications outside of “initialize” @bsideup

Slide 97

Slide 97 text

✓ Super easy to maintain ✓ Works with any JVM language ✓ “Controllable mutability” - no modifications outside of “initialize” ✓ Void-retuning “setX” can easily be used @bsideup

Slide 98

Slide 98 text

✓ Super easy to maintain ✓ Works with any JVM language ✓ “Controllable mutability” - no modifications outside of “initialize” ✓ Void-retuning “setX” can easily be used ✓ Can use “if-else” @bsideup

Slide 99

Slide 99 text

GraalVM focus

Slide 100

Slide 100 text

@bsideup https://medium.com/graalvm/using-testcontainers-from-a-node-js- application-3aa2273bf3bb

Slide 101

Slide 101 text

@bsideup // JavaScript var GenericContainer = Java.type(’org.testcontainers.containers.GenericContainer’); var container = new GenericContainer("nginx"); container.setExposedPorts([80]); container.start(); console.log(container.getContainerIpAddress() + ‘:’ + container.getMappedPort(80)); https://medium.com/graalvm/using-testcontainers-from-a-node-js- application-3aa2273bf3bb

Slide 102

Slide 102 text

@bsideup // JavaScript var GenericContainer = Java.type(’org.testcontainers.containers.GenericContainer’); var container = new GenericContainer("nginx"); container.setExposedPorts([80]); container.start(); console.log(container.getContainerIpAddress() + ‘:’ + container.getMappedPort(80)); https://medium.com/graalvm/using-testcontainers-from-a-node-js- application-3aa2273bf3bb // Python import java generic = java.type('org.testcontainers.containers.GenericContainer') container = generic('nginx') container.setExposedPorts([80]) container.start(); print('%s:%s' % (container.getContainerIpAddress(), container.getMappedPort(80)));

Slide 103

Slide 103 text

“container core”

Slide 104

Slide 104 text

Testcontainers 1.x architecture Core
 (Docker, GenericContainer, 
 JUnit 4 int, test lifecycle) @bsideup

Slide 105

Slide 105 text

Testcontainers 1.x architecture Core
 (Docker, GenericContainer, 
 JUnit 4 int, test lifecycle) JUnit Jupiter int. @bsideup

Slide 106

Slide 106 text

Testcontainers 1.x architecture Core
 (Docker, GenericContainer, 
 JUnit 4 int, test lifecycle) JUnit Jupiter int. Modules like MySQL/Kafka/… @bsideup

Slide 107

Slide 107 text

Testcontainers 2.x architecture Container-core
 (GenericContainer) @bsideup

Slide 108

Slide 108 text

Testcontainers 2.x architecture Container-core
 (GenericContainer) Test Frameworks JUnit 4 JUnit Jupiter Scala test? … @bsideup

Slide 109

Slide 109 text

Testcontainers 2.x architecture Container-core
 (GenericContainer) Test Frameworks JUnit 4 JUnit Jupiter Scala test? … Executing engines Container-core-docker
 (env discovery, networks, …) Container-core-k8s? … @bsideup

Slide 110

Slide 110 text

Modules like MySQL/Kafka/… Testcontainers 2.x architecture Container-core
 (GenericContainer) Test Frameworks JUnit 4 JUnit Jupiter Scala test? … Executing engines Container-core-docker
 (env discovery, networks, …) Container-core-k8s? … @bsideup

Slide 111

Slide 111 text

No content

Slide 112

Slide 112 text

Usability Flexibility Speed Features @bsideup

Slide 113

Slide 113 text

Usability Flexibility Speed Features @bsideup

Slide 114

Slide 114 text

Usability Flexibility Speed Features @bsideup

Slide 115

Slide 115 text

Please welcome…

Slide 116

Slide 116 text

Reusable containers

Slide 117

Slide 117 text

Reusable containers

Slide 118

Slide 118 text

Demo

Slide 119

Slide 119 text

@bsideup

Slide 120

Slide 120 text

✓ Ultra-fast ITDD (Integration Test Driven Development) @bsideup

Slide 121

Slide 121 text

✓ Ultra-fast ITDD (Integration Test Driven Development) ✓ Minimal effort for the users @bsideup

Slide 122

Slide 122 text

✓ Ultra-fast ITDD (Integration Test Driven Development) ✓ Minimal effort for the users ✓ Works for most of the containers @bsideup

Slide 123

Slide 123 text

✓ Ultra-fast ITDD (Integration Test Driven Development) ✓ Minimal effort for the users ✓ Works for most of the containers ✓ Eventually cleanups stale containers
 (unlike Docker Compose) @bsideup

Slide 124

Slide 124 text

✓ Ultra-fast ITDD (Integration Test Driven Development) ✓ Minimal effort for the users ✓ Works for most of the containers ✓ Eventually cleanups stale containers
 (unlike Docker Compose) ✓ Available later this year. Pre-order now for $0 @bsideup

Slide 125

Slide 125 text

Takeaways • https://testcontainers.org • Works on Linux, Mac and Windows • …including CIs like Jenkins, Travis, CircleCI, BitBucket, AzurePipelines, … • Provides a great balance between 
 flexibility, usability, speed and features
 @bsideup

Slide 126

Slide 126 text

@bsideup bsideup