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.
1.0.0Add 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
Configure Testcontainers for Java, Python, and Node.js environments.
Implement advanced container configurations for databases, message brokers, and custom services.
Integrate Testcontainers with Docker Compose and custom wait strategies for complex setups.
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.