Skill

Master Testcontainers for Robust Integration Testing

Skill for setting up Testcontainers integration tests in Java, Python, and Node.js with databases, brokers, and CI tuning.

Maintainer of this project? Claim this page to edit the listing.


79
Spark score
out of 100
Updated 7 months ago
Version 1.0.0
Models

Add to Favorites

Why it matters

Streamline your integration testing by leveraging Testcontainers to spin up and manage disposable Docker containers for databases, message brokers, and more. Ensure reliable, isolated, and production-parity testing environments with expert setup and configuration.

Outcomes

What it gets done

01

Configure Testcontainers for Java, Python, and Node.js environments.

02

Implement advanced container configurations for databases, message brokers, and custom services.

03

Integrate Testcontainers with Docker Compose and custom wait strategies for complex setups.

04

Optimize Testcontainers usage for CI/CD pipelines and local development with best practices.

Install

Add it to your toolbox

Run in your project directory:

curl -fsSL https://spark.entire.vc/get/vb-testcontainers-setup | bash

Overview

Testcontainers Setup Expert

A skill for configuring Testcontainers-based integration tests across Java, Python, and Node.js - database/broker containers, wait strategies, networking, and CI-specific tuning. Use when integration tests need a real containerized dependency rather than a mock; not for unit tests or production deployment.

What it does

Testcontainers Setup Expert is a skill for configuring Testcontainers - the library that provisions throwaway Docker containers (databases, message brokers, browsers, or any containerizable dependency) for integration testing. Core principles: let Testcontainers manage container startup/cleanup automatically, use fresh containers per test suite for isolation, reuse containers only when it doesn't compromise reliability, pick images that match production closely, and always ensure graceful container teardown after tests. It gives working setup examples in three languages:

### pip install testcontainers[postgresql]

from testcontainers.postgres import PostgresContainer
import pytest

@pytest.fixture(scope="session")
def postgres_container():
    with PostgresContainer(
        image="postgres:15",
        dbname="testdb",
        username="test",
        password="test"
    ) as container:
        yield container

def test_database_operations(postgres_container):
    connection_url = postgres_container.get_connection_url()
    # Your test logic here

Java setup uses the @Testcontainers/@Container JUnit 5 annotations with a PostgreSQLContainer, and Node.js uses the testcontainers npm package's PostgreSqlContainer with beforeAll/afterAll lifecycle hooks.

When to use - and when NOT to

Use it when integration tests need a real instance of a dependency rather than a mock or an in-memory substitute - databases (PostgreSQL with withInitScript/withTmpFs, MySQL with a custom my.cnf override), message brokers (Kafka with an embedded Zookeeper, Redis with a password-protected GenericContainer), or a full stack via Docker Compose integration (DockerComposeContainer exposing specific services and waiting on a listening port). It is not meant for unit tests that shouldn't depend on Docker being available, or for production deployment - it is strictly a test-time tool.

Inputs and outputs

Input is the dependency image and configuration; output is a running, addressable container for the test to connect to. It documents custom wait strategies (Wait.forHttp("/health").forStatusCode(200) with a startup timeout, or Wait.forLogMessage() matching a regex), shared Docker networks so multiple containers can reach each other by alias (withNetwork/withNetworkAliases/dependsOn), and container reuse via .withReuse(true) plus a testcontainers.reuse.enable=true label - explicitly framed as a development-time optimization to disable in CI. A singleton wrapper pattern is given for sharing one expensive container instance across a test run.

Integrations

Configuration can be externalized to a testcontainers.properties file (testcontainers.reuse.enable, testcontainers.ryuk.disabled, testcontainers.docker.socket.override). For CI/CD it gives a GitHub Actions example that disables reuse and the Ryuk cleanup container via TESTCONTAINERS_RYUK_DISABLED, then runs ./mvnw verify. Troubleshooting guidance covers avoiding hardcoded ports (container.getMappedPort()), tuning wait strategies for slow-starting applications, keeping the Ryuk container running for automatic cleanup, verifying Docker socket permissions, and controlling image freshness with .withImagePullPolicy(PullPolicy.ageBased(...)).

Who it's for

Backend engineers and QA/test-infrastructure engineers writing integration tests that need real databases, brokers, or multi-service environments, and who need to balance test isolation against startup performance - especially when tuning the same test suite differently for local development (reuse enabled) versus CI (reuse disabled, isolated containers per run).

Source README

Testcontainers Setup Expert

You are an expert in Testcontainers, the testing library that provides lightweight, throwaway instances of common databases, message brokers, web browsers, or anything else that can run in a Docker container for integration testing.

Core Principles

  • Container Lifecycle Management: Testcontainers automatically manages container startup, configuration, and cleanup
  • Test Isolation: Each test suite or class should use fresh container instances to ensure isolation
  • Resource Efficiency: Reuse containers when safe, but prioritize test reliability over performance
  • Environment Parity: Use container images that closely match production environments
  • Graceful Cleanup: Always ensure containers are properly stopped and removed after tests

Language-Specific Setup

Java Setup

// Maven dependency
<dependency>
    <groupId>org.testcontainers</groupId>
    <artifactId>testcontainers</artifactId>
    <version>1.19.0</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.testcontainers</groupId>
    <artifactId>postgresql</artifactId>
    <version>1.19.0</version>
    <scope>test</scope>
</dependency>

// JUnit 5 Integration
@Testcontainers
class DatabaseIntegrationTest {
    
    @Container
    static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:15")
            .withDatabaseName("testdb")
            .withUsername("test")
            .withPassword("test")
            .withReuse(false);
    
    @Test
    void testDatabaseConnection() {
        String jdbcUrl = postgres.getJdbcUrl();
        // Your test logic here
    }
}

Python Setup

### pip install testcontainers[postgresql]

from testcontainers.postgres import PostgresContainer
import pytest

@pytest.fixture(scope="session")
def postgres_container():
    with PostgresContainer(
        image="postgres:15",
        dbname="testdb",
        username="test",
        password="test"
    ) as container:
        yield container

def test_database_operations(postgres_container):
    connection_url = postgres_container.get_connection_url()
    # Your test logic here

Node.js Setup

// npm install testcontainers

const { GenericContainer, PostgreSqlContainer } = require('testcontainers');

describe('Database Integration Tests', () => {
    let container;
    
    beforeAll(async () => {
        container = await new PostgreSqlContainer('postgres:15')
            .withDatabase('testdb')
            .withUsername('test')
            .withPassword('test')
            .withExposedPorts(5432)
            .start();
    });
    
    afterAll(async () => {
        await container.stop();
    });
    
    test('should connect to database', async () => {
        const connectionUri = container.getConnectionUri();
        // Your test logic here
    });
});

Common Container Configurations

Database Containers

// PostgreSQL with custom configuration
PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:15")
    .withDatabaseName("myapp")
    .withUsername("appuser")
    .withPassword("secret")
    .withInitScript("init.sql")
    .withTmpFs(Map.of("/var/lib/postgresql/data", "rw"));

// MySQL with custom my.cnf
MySQLContainer<?> mysql = new MySQLContainer<>("mysql:8.0")
    .withDatabaseName("testdb")
    .withUsername("test")
    .withPassword("test")
    .withConfigurationOverride("mysql-config");

Message Brokers

// Kafka setup
KafkaContainer kafka = new KafkaContainer(DockerImageName.parse("confluentinc/cp-kafka:7.4.0"))
    .withEmbeddedZookeeper();

// Redis setup
GenericContainer<?> redis = new GenericContainer<>("redis:7-alpine")
    .withExposedPorts(6379)
    .withCommand("redis-server", "--requirepass", "mypassword");

Advanced Configuration Patterns

Docker Compose Integration

@Container
static DockerComposeContainer environment = new DockerComposeContainer(
        new File("src/test/resources/docker-compose-test.yml")
    )
    .withExposedService("database", 5432)
    .withExposedService("redis", 6379)
    .withLocalCompose(true)
    .waitingFor("database", Wait.forListeningPort());

Custom Wait Strategies

GenericContainer<?> app = new GenericContainer<>("myapp:latest")
    .withExposedPorts(8080)
    .waitingFor(Wait.forHttp("/health")
        .forStatusCode(200)
        .withStartupTimeout(Duration.ofMinutes(2)))
    .waitingFor(Wait.forLogMessage(".*Application started.*", 1));

Network Configuration

// Shared network for multiple containers
Network network = Network.newNetwork();

PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:15")
    .withNetwork(network)
    .withNetworkAliases("database");

GenericContainer<?> app = new GenericContainer<>("myapp:latest")
    .withNetwork(network)
    .withEnv("DB_HOST", "database")
    .dependsOn(postgres);

Best Practices

Resource Management

// Use @Testcontainers and static containers for class-level lifecycle
@Testcontainers
class IntegrationTest {
    @Container
    static final PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:15")
        .withReuse(true) // Enable for development, disable for CI
        .withLabel("testcontainers.reuse.enable", "true");
}

Configuration Externalization

### testcontainers.properties
testcontainers.reuse.enable=true
testcontainers.ryuk.disabled=false
testcontainers.docker.socket.override=/var/run/docker.sock

CI/CD Optimization

### GitHub Actions example
- name: Setup Testcontainers
  run: |
    echo "testcontainers.reuse.enable=false" >> ~/.testcontainers.properties
    echo "testcontainers.ryuk.disabled=true" >> ~/.testcontainers.properties

- name: Run integration tests
  run: ./mvnw verify -Dspring.profiles.active=test
  env:
    TESTCONTAINERS_RYUK_DISABLED: true

Troubleshooting Tips

  • Port Conflicts: Use container.getMappedPort() instead of hardcoded ports
  • Slow Startup: Implement proper wait strategies and increase timeouts for complex applications
  • Resource Cleanup: Ensure Ryuk container is running for automatic cleanup
  • Docker Socket: Verify Docker socket permissions and accessibility
  • Image Pulling: Use .withImagePullPolicy(PullPolicy.ageBased(Duration.ofDays(1))) to control image updates

Performance Optimization

// Singleton pattern for expensive containers
public class SharedPostgreSQLContainer extends PostgreSQLContainer<SharedPostgreSQLContainer> {
    private static final String IMAGE_VERSION = "postgres:15";
    private static SharedPostgreSQLContainer container;
    
    private SharedPostgreSQLContainer() {
        super(IMAGE_VERSION);
    }
    
    public static SharedPostgreSQLContainer getInstance() {
        if (container == null) {
            container = new SharedPostgreSQLContainer()
                .withDatabaseName("testdb")
                .withUsername("test")
                .withPassword("test");
        }
        return container;
    }
}

Always balance performance optimizations with test isolation requirements. Use container reuse judiciously and ensure proper test data cleanup between tests.

FAQ

Common questions

Discussion

Questions & comments ยท 0

Sign In Sign in to leave a comment.