DEV Community

Alex Spinov
Alex Spinov

Posted on

Testcontainers Has a Free Integration Testing Library — Real Databases in Your Tests

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');
  });
});
Enter fullscreen mode Exit fullscreen mode

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();
Enter fullscreen mode Exit fullscreen mode

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)}`;
Enter fullscreen mode Exit fullscreen mode

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`;
Enter fullscreen mode Exit fullscreen mode

5. Reusable Containers (Fast CI)

const container = await new PostgreSqlContainer()
  .withReuse()  // Container persists between test runs
  .start();
Enter fullscreen mode Exit fullscreen mode

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/TypeScripttestcontainers
  • Java — Original Testcontainers (most mature)
  • Pythontestcontainers-python
  • Gotestcontainers-go
  • .NETTestcontainers.NET
  • Rusttestcontainers-rs

Getting Started

npm install testcontainers @testcontainers/postgresql
Enter fullscreen mode Exit fullscreen mode

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)