0
0
MicroservicesHow-ToIntermediate ยท 4 min read

How to Use Testcontainers for Microservices Testing

Use Testcontainers to spin up real Docker containers for each microservice during tests, ensuring isolated and realistic environments. Define containers in your test code, configure networking between them, and run integration tests against these live services.
๐Ÿ“

Syntax

Testcontainers uses container classes to define and manage Docker containers in tests. You create containers for each microservice, configure ports and environment variables, and start them before tests run.

  • GenericContainer: Base container class for any Docker image.
  • withExposedPorts(): Opens ports to communicate with the container.
  • withEnv(): Sets environment variables inside the container.
  • start(): Starts the container before tests.
  • stop(): Stops the container after tests.
java
GenericContainer<?> serviceA = new GenericContainer<>("service-a-image:latest")
    .withExposedPorts(8080)
    .withEnv("CONFIG", "value");

GenericContainer<?> serviceB = new GenericContainer<>("service-b-image:latest")
    .withExposedPorts(9090)
    .withEnv("DEPENDENCY_URL", "http://service-a:8080");

serviceA.start();
serviceB.start();

// Run tests here

serviceB.stop();
serviceA.stop();
๐Ÿ’ป

Example

This example shows how to use Testcontainers to start two microservices containers and test their interaction. It demonstrates container setup, networking, and a simple HTTP request to verify service communication.

java
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.testcontainers.containers.GenericContainer;
import org.testcontainers.utility.DockerImageName;

import java.net.HttpURLConnection;
import java.net.URL;

import static org.junit.jupiter.api.Assertions.assertEquals;

public class MicroservicesTest {

    static GenericContainer<?> serviceA = new GenericContainer<>(DockerImageName.parse("service-a-image:latest"))
            .withExposedPorts(8080);

    static GenericContainer<?> serviceB = new GenericContainer<>(DockerImageName.parse("service-b-image:latest"))
            .withExposedPorts(9090);

    @BeforeAll
    public static void setUp() {
        serviceA.start();
        serviceB.withEnv("SERVICE_A_URL", "http://" + serviceA.getHost() + ":" + serviceA.getFirstMappedPort());
        serviceB.start();
    }

    @AfterAll
    public static void tearDown() {
        serviceB.stop();
        serviceA.stop();
    }

    @Test
    public void testServiceBCallsServiceA() throws Exception {
        String serviceBUrl = "http://" + serviceB.getHost() + ":" + serviceB.getFirstMappedPort() + "/call-service-a";
        HttpURLConnection connection = (HttpURLConnection) new URL(serviceBUrl).openConnection();
        connection.setRequestMethod("GET");
        int responseCode = connection.getResponseCode();
        assertEquals(200, responseCode);
    }
}
Output
Tests run successfully with HTTP 200 response confirming service interaction.
โš ๏ธ

Common Pitfalls

  • Not waiting for containers to be ready: Containers may start but services inside might not be ready immediately. Use health checks or wait strategies.
  • Incorrect port mapping: Forgetting to expose and map ports causes connection failures.
  • Hardcoding container hostnames: Use dynamic host and port from Testcontainers, not fixed values.
  • Not stopping containers: Can cause resource leaks and test interference.
java
/* Wrong: Hardcoded port and hostname */
String url = "http://localhost:8080/api";

/* Right: Use Testcontainers dynamic host and port */
String url = "http://" + serviceA.getHost() + ":" + serviceA.getFirstMappedPort() + "/api";
๐Ÿ“Š

Quick Reference

FeatureDescriptionUsage
GenericContainerBase class to create any Docker containernew GenericContainer<>("image:tag")
withExposedPortsExpose ports to communicate with container.withExposedPorts(8080)
withEnvSet environment variables inside container.withEnv("KEY", "value")
startStart container before tests.start()
stopStop container after tests.stop()
getHost & getFirstMappedPortGet container host and mapped portcontainer.getHost(), container.getFirstMappedPort()
Wait strategiesWait for container readinesswithStartupTimeout(), waitingFor()
โœ…

Key Takeaways

Use Testcontainers to run real Docker containers for each microservice during integration tests.
Always expose and map ports correctly and use dynamic host and port from Testcontainers.
Implement wait strategies to ensure services inside containers are ready before testing.
Stop containers after tests to free resources and avoid conflicts.
Configure environment variables to simulate real microservice dependencies.