Back-end engineering nowadays may require the integration of multiple services. It is painful for engineers to install many services in their local development environment. Docker has provided an easier way to do this, but it will still require some scripting outside our code. It also has not exactly had a perfect solution . This problem has been addressed by Testcontainers [ ]. if we wanted to test smaller functions or classes instead of the whole service 1 This article will give you an understanding of what is Testcontainers, why should you use Testcontainers, and finally how to use it. While this article will only be based on experience on Java programming language, Testcontainers does has support for other programming languages, like go [ ] and node-js [ ]. 2 3 What is Testcontainers Testcontainers is a lightweight library that “wrap” docker and connect to test frameworks, such as Junit and Spock. It is also modular, as you can see in . This means, you can import only the necessary stacks that matter to you. their maven repositories Why Should You Use Testcontainers Testcontainers will definitely make software engineer life simpler! Docker and Docker Compose have made deploying service in local development and production environments simpler. They are like lightweight virtual machine, thus whatever you do in your current environment will not affect services contained in docker environment. However you will need to tell docker run the service outside of your currently opened IDE. Using Tescontainers will make this even shorter. You can use ctrl + F10 directly in Intellij IDEA. Another interesting aspect that makes Testcontainers shines is, it will create disposable instances. It means you can write your test with assumption that each test can be executed independently. You probably do not want to retain whatever inserted into your PostgreSQL database in one test. Retaining that test records may cause another test failing. This causing your tests having a bug. Wait! are you testing so that your main codes are bug-free?? Testcontainers will enable us to use multiple tech stacks in one test class. For instance, you need to test a Kafka consumer that does insert to a Mysql instance. We can define both services in the same class! How to use Testcontainers Testcontainers is best to be used in an integration test, although it can run within Junit runtime. Typically you wanted to run one unit test class in a few hundreds milliseconds. Running many instances of Testcontainers in a unit tests will result in a significantly slow unit test, and you do not want to run ~ 20s per unit test. It Takes Few Lines Let say you wanted to use Kafka, you can easily define your Kafka instance in your test file as { KafkaContainer kafka; { kafka = KafkaContainer( ); kafka.start(); } } public class JavaKafkaTest @Rule public @Before Exception public void setUp () throws new "5.4.2" This code will download Confluent platform’s docker image file with version (Kafka version ) and start the docker, all in few lines of codes. 5.4.2 2.5.0 Kafka is coupled with zookeeper, however, by default it has been handled by Testcontainers. If you wish to use your own zookeeper for some reason, then luckily there’s an option for it. Full Example: Producing Messages and Consuming with Kafka Streams I have shown you a little on how to make a instance, now I will demonstrate full class. KafkaContainer { KafkaContainer kafka; { kafka = KafkaContainer( ); kafka.start(); Properties adminProperties = Properties(); adminProperties.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, kafka.getBootstrapServers()); AdminClient adminClient = KafkaAdminClient.create(adminProperties); adminClient.createTopics( Stream.of( ) .map(n -> NewTopic(n, , ( ) )) .collect(Collectors.toList()) ).all().get(); } { prepareSeedMessages(); Topology topology = prepareKafkaTopology(); KafkaStreams streams = KafkaStreams(topology, consumerProps()); streams.start(); Thread.sleep( ); } { KafkaProducer producer = KafkaProducer(producerProps()); producer.send( ProducerRecord( , )); producer.send( ProducerRecord( , )); producer.send( ProducerRecord( , )); producer.close(); } { StreamsBuilder streamsBuilder = StreamsBuilder(); streamsBuilder .<String, String>stream( ) .peek((k, v) -> { Logger log = Logger.getLogger(Thread.currentThread().getName()); log.info(String.format( , v)); }) .flatMapValues(v -> Arrays.asList(v.split( ))) .peek((k, v) -> { Logger log = Logger.getLogger(Thread.currentThread().getName()); log.info(String.format( , v)); }) .groupBy((k, v) -> v) .count(); streamsBuilder.build(); } { Properties consumerProps = Properties(); consumerProps.put(StreamsConfig.DEFAULT_KEY_SERDE_CLASS_CONFIG, Serdes.String().getClass()); consumerProps.put(StreamsConfig.DEFAULT_VALUE_SERDE_CLASS_CONFIG, Serdes.String().getClass()); consumerProps.put(StreamsConfig.APPLICATION_ID_CONFIG, ); consumerProps.put(StreamsConfig.BOOTSTRAP_SERVERS_CONFIG, kafka.getBootstrapServers()); consumerProps; } { Properties producerProperties = Properties(); producerProperties.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, kafka.getBootstrapServers()); producerProperties.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName()); producerProperties.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName()); producerProperties; } { kafka.stop(); } } public class JavaKafkaTest @Rule public @Before Exception public void setUp () throws new "5.4.2" new "plaintext-input" new 1 short 1 @Test InterruptedException public void testKafkaTestcontainer () throws final final new 5000 private void prepareSeedMessages () new new "plaintext-input" "this is sparta" new "plaintext-input" "this is sparta" new "plaintext-input" "this is sparta" Topology private prepareKafkaTopology () final new "plaintext-input" "receive message from plaintext-input : %s" "\\W+" "receive message from count : %s" return Properties private consumerProps () new "sample-app" return Properties private producerProps () new return @After public void tearDown () On method, all the necessary components are prepared. must be started, before other calls. Remember will be executed for every test method, so that will make our tests immutable. setUp KafkaContainer @Before But on the other hand, this will made the container go up and down between the test method. Function is the runnable part. This is where you do stuff and assert all the things you need. This example demonstrates complete publish and subscribe in a single method. testKafkaContainer procedure will populate the Kafka queue with three strings. I set the standard, required attributes: key serializer, value serializer, application id, and bootstrap server. It is important that when setting the producer’s properties, I took , instead of hard-coding it by yourself. prepareSeedMessages a bootstrap server from Kafka container instance We then prepare for creating a Kafka stream’s topology. [4]. I will use a "necessary" word count topology example. Topology is basically a directed acyclic graph (DAG) that defines the sequence of processing Here, I have defined to take the input from the topic “plaintext-input”. I used peek to execute a logging procedure and return what that procedure received. Then, will break each token by whitespaces. I then can group by each word and do count. flatMapValues Finally, I used method to stop the Kafka container. This method will destroy the container and delete all the data inside this container. tearDown If we have multiple tests in one class, it will be better if we move start and stop method to and . Using and like this example will results in container created and destroyed along every single test method execution. It will make running this test class very long. Instead we can declare construct once for each test class. @BeforeClass @AfterClass @Before @After Conclusion Here I have explained briefly that Testcontainers is a library that encapsulates docker and makes it runnable along with the testing library. I have also laid out some reasons to use Testcontainers: directly run in your IDE and run your tests independently. Lastly, I have gives an example of using Kafka in Testcontainers. References [1] Testcontainer Documentation. https://www.testcontainers.org/ [2] testcontainer-go repository. https://github.com/testcontainers/testcontainers-go [3] testcontainer-node repository. https://github.com/testcontainers/testcontainers-node [4] Optimizing Streams. https://docs.confluent.io/current/streams/developer-guide/optimizing-streams.html