Mocking your database in tests? That's how bugs reach production. Testcontainers spins up real PostgreSQL, Redis, Kafka — in Docker — for each test run.
What is Testcontainers?
Testcontainers is a library that provides lightweight, disposable Docker containers for integration testing. Real databases, real message brokers, real services — automatically started and cleaned up.
Why Testcontainers
1. Real Database in Tests
import { PostgreSqlContainer } from '@testcontainers/postgresql';
describe('UserRepository', () => {
let container;
let db;
beforeAll(async () => {
container = await new PostgreSqlContainer().start();
db = new Pool({ connectionString: container.getConnectionUri() });
await db.query('CREATE TABLE users (id SERIAL, name TEXT, email TEXT)');
});
afterAll(async () => {
await container.stop();
});
test('creates user', async () => {
await db.query("INSERT INTO users (name, email) VALUES ($1, $2)", ['Alice', 'alice@test.com']);
const result = await db.query("SELECT * FROM users WHERE name = 'Alice'");
expect(result.rows[0].email).toBe('alice@test.com');
});
});
2. Multiple Services
import { RedisContainer } from '@testcontainers/redis';
import { KafkaContainer } from '@testcontainers/kafka';
import { MongoDBContainer } from '@testcontainers/mongodb';
import { ElasticsearchContainer } from '@testcontainers/elasticsearch';
import { MySqlContainer } from '@testcontainers/mysql';
// Each test suite gets its own isolated containers
const redis = await new RedisContainer().start();
const kafka = await new KafkaContainer().start();
const mongo = await new MongoDBContainer().start();
3. Custom Containers
import { GenericContainer, Wait } from 'testcontainers';
const container = await new GenericContainer('my-custom-image:latest')
.withExposedPorts(8080)
.withEnvironment({ NODE_ENV: 'test', API_KEY: 'test-key' })
.withWaitStrategy(Wait.forHttp('/health', 8080))
.start();
const url = `http://${container.getHost()}:${container.getMappedPort(8080)}`;
4. Docker Compose
import { DockerComposeEnvironment } from 'testcontainers';
const environment = await new DockerComposeEnvironment('.', 'docker-compose.test.yml')
.withWaitStrategy('db', Wait.forHealthCheck())
.withWaitStrategy('redis', Wait.forLogMessage('Ready to accept connections'))
.up();
const dbContainer = environment.getContainer('db');
const dbUrl = `postgresql://test:test@${dbContainer.getHost()}:${dbContainer.getMappedPort(5432)}/test`;
5. Reusable Containers (Fast CI)
const container = await new PostgreSqlContainer()
.withReuse() // Container persists between test runs
.start();
Testcontainers vs Mocks
| Testcontainers | Mocks | |
|---|---|---|
| Confidence | High (real service) | Low (simulated) |
| SQL compatibility | Tested against real DB | Untested |
| Edge cases | Caught naturally | Manually written |
| Speed | Slower (containers) | Fast |
| Setup | Docker required | None |
| Migrations | Actually tested | Not tested |
Supported Languages
-
Node.js/TypeScript —
testcontainers - Java — Original Testcontainers (most mature)
-
Python —
testcontainers-python -
Go —
testcontainers-go -
.NET —
Testcontainers.NET -
Rust —
testcontainers-rs
Getting Started
npm install testcontainers @testcontainers/postgresql
The Bottom Line
Testcontainers eliminates the gap between tests and production. Real databases, real services, real confidence. If your mocks passed but production broke — you needed Testcontainers.
Need data tools? I build scraping solutions. Check my Apify actors or email spinov001@gmail.com.
Top comments (0)