Home > Blog > Desenvolvimento Web
Desenvolvimento Web
JavaScript
Programação

Testcontainers no Node.js

Atualizado em: 25 de setembro de 2026

Rack de servidores processando fluxos de dados no Node.js

Testcontainers permite criar dependências reais e descartáveis durante testes Node.js. Em vez de simular PostgreSQL, Redis, Kafka ou outro serviço com mocks, a suíte inicia containers Docker, aguarda ficarem prontos, executa testes e remove os recursos no final.

Essa abordagem aumenta a fidelidade dos testes de integração. Consultas SQL, migrations, índices, autenticação, protocolos e comportamento de rede são exercitados contra o software real, em uma versão controlada pela imagem.

Quando usar

Testcontainers é indicado para testar:

  • repositórios e migrations;
  • filas e brokers;
  • cache Redis;
  • armazenamento S3 compatível;
  • Elasticsearch ou OpenSearch;
  • serviços locais simulados como LocalStack;
  • integrações entre vários componentes;
  • falhas de rede com ToxiProxy.

Testes unitários rápidos continuam importantes. Use containers onde o comportamento externo é parte do risco.

Requisitos

O ambiente precisa de um runtime compatível, normalmente Docker. Valide:

docker version
docker run --rm hello-world

Em CI, o runner deve permitir containers e acesso ao daemon. Restrições de segurança, Docker rootless e ambientes remotos podem exigir configuração.

Instalação

npm install -D testcontainers

Para PostgreSQL, é possível usar módulo específico:

npm install -D @testcontainers/postgresql pg

Primeiro container genérico

import { GenericContainer } from 'testcontainers';

const container = await new GenericContainer('redis:8-alpine')
  .withExposedPorts(6379)
  .start();

const host = container.getHost();
const port = container.getMappedPort(6379);

console.log({ host, port });

await container.stop();

Não assuma que a porta externa será 6379. Testcontainers escolhe uma porta livre e fornece o mapeamento.

PostgreSQL com módulo

import { PostgreSqlContainer } from '@testcontainers/postgresql';
import pg from 'pg';

const postgres = await new PostgreSqlContainer('postgres:18-alpine')
  .withDatabase('app_test')
  .withUsername('app')
  .withPassword('senha_teste')
  .start();

const pool = new pg.Pool({
  connectionString: postgres.getConnectionUri(),
});

await pool.query('select 1');

await pool.end();
await postgres.stop();

O módulo encapsula variáveis, portas e URI de conexão.

Integração com Node Test Runner

import { before, after, test } from 'node:test';
import assert from 'node:assert/strict';
import { PostgreSqlContainer } from '@testcontainers/postgresql';
import pg from 'pg';

let container;
let pool;

before(async () => {
  container = await new PostgreSqlContainer('postgres:18-alpine').start();
  pool = new pg.Pool({ connectionString: container.getConnectionUri() });
  await pool.query('create table users(id serial primary key, name text not null)');
});

after(async () => {
  await pool?.end();
  await container?.stop();
});

test('grava e consulta usuário', async () => {
  const inserted = await pool.query(
    'insert into users(name) values($1) returning id, name',
    ['Ana'],
  );
  assert.equal(inserted.rows[0].name, 'Ana');
});

Setup global ou por arquivo

Iniciar um container por teste pode ser lento. Estratégias:

  • um container por arquivo de teste;
  • um container por suíte;
  • container compartilhado com banco limpo entre testes;
  • container isolado por worker em execução paralela.

Compartilhar reduz tempo, mas aumenta risco de vazamento de estado. Escolha isolamento primeiro e otimize depois de medir.

Migrations reais

Execute a mesma ferramenta usada em produção:

before(async () => {
  container = await new PostgreSqlContainer('postgres:18-alpine').start();
  process.env.DATABASE_URL = container.getConnectionUri();
  await executarMigrations();
});

Isso detecta migrations inválidas, ordem incorreta, extensões ausentes e diferenças de versão.

Wait strategies

Uma porta aberta não significa que o serviço está pronto. Use estratégia adequada:

import { GenericContainer, Wait } from 'testcontainers';

const container = await new GenericContainer('minio/minio')
  .withCommand(['server', '/data'])
  .withExposedPorts(9000)
  .withWaitStrategy(Wait.forLogMessage(/API:/))
  .start();

Também existem espera por healthcheck, HTTP, logs e outras condições. Prefira o sinal oficial de prontidão do serviço.

Variáveis de ambiente e comandos

const container = await new GenericContainer('minio/minio')
  .withEnvironment({
    MINIO_ROOT_USER: 'teste',
    MINIO_ROOT_PASSWORD: 'senha-segura-de-teste',
  })
  .withCommand(['server', '/data'])
  .withExposedPorts(9000)
  .start();

Credenciais de teste devem ser exclusivas do ambiente descartável.

Arquivos e inicialização

É possível copiar arquivos:

const container = await new GenericContainer('postgres:18-alpine')
  .withCopyFilesToContainer([
    {
      source: './test/fixtures/init.sql',
      target: '/docker-entrypoint-initdb.d/init.sql',
    },
  ])
  .start();

Para cenários complexos, preferir migrations programáticas pode produzir erros mais claros.

Redes entre containers

Crie uma rede para serviços se comunicarem por alias:

import { Network, GenericContainer } from 'testcontainers';

const network = await new Network().start();

const redis = await new GenericContainer('redis:8-alpine')
  .withNetwork(network)
  .withNetworkAliases('redis')
  .withExposedPorts(6379)
  .start();

const app = await new GenericContainer('minha-app:test')
  .withNetwork(network)
  .withEnvironment({ REDIS_URL: 'redis://redis:6379' })
  .start();

Da máquina de testes, use host e porta mapeada. Entre containers, use alias e porta interna.

Docker Compose

Testcontainers pode iniciar um arquivo Compose existente. Isso é útil para cenários que já descrevem vários serviços, mas um teste programático costuma oferecer controle mais fino e melhor isolamento.

Imagens fixas

Evite tags flutuantes como latest. Use:

postgres:18.1-alpine
redis:8.2-alpine

Fixar digest oferece ainda mais reprodutibilidade. Atualize em pull request dedicado.

Logs de container

Em falhas, capture logs:

const stream = await container.logs();
stream
  .on('data', (line) => console.log(line.toString()))
  .on('err', (line) => console.error(line.toString()));

Evite imprimir segredos. No CI, preserve logs como artefato somente quando necessário.

Timeouts

Primeiro download pode levar mais tempo. Ajuste timeout da suíte sem esconder travamentos. Separe timeout de inicialização, consulta e encerramento.

Reutilização de containers

Reuso pode acelerar desenvolvimento local, mas reduz isolamento e pode deixar estado antigo. Em CI, prefira containers descartáveis. Se habilitar reuso local, documente como limpar.

Execução paralela

Portas aleatórias permitem paralelismo, mas banco e nomes de recursos precisam ser únicos. Não use uma variável global de conexão compartilhada por workers diferentes.

CI com GitHub Actions

- uses: actions/checkout@v4
- uses: actions/setup-node@v4
  with:
    node-version: 24
- run: npm ci
- run: npm test

Runners hospedados normalmente possuem Docker. Em runners próprios, valide permissões e espaço em disco.

Limpeza e Ryuk

Testcontainers usa mecanismos para remover recursos, inclusive quando testes falham. Não desative o reaper sem entender o impacto. Processos mortos abruptamente podem deixar containers; configure limpeza periódica no runner.

Testes de falha de rede

Com ToxiProxy, simule latência, timeout, perda de conexão e largura de banda. Isso ajuda a validar retries, circuit breakers e idempotência sem alterar o serviço real.

Testcontainers Cloud

Ambientes sem Docker local podem usar runtime remoto compatível. Considere latência, segurança, acesso à rede e custo.

Segurança

Acesso ao daemon Docker é altamente privilegiado. Não execute código não confiável com acesso irrestrito. Pull requests externos precisam de isolamento e ausência de segredos.

Testcontainers ou mocks

Mocks são rápidos e bons para lógica local. Containers validam integração real. Uma pirâmide equilibrada usa muitos testes unitários, alguns testes com containers e poucos testes de ponta a ponta.

Fluxo recomendado

Fixe imagens, use wait strategy real, execute migrations, isole dados, encerre clientes antes dos containers e preserve logs de falhas. Combine com Node Test Runner, transações em PostgreSQL no Node.js, filas em processamento de jobs e pipelines de GitHub Actions.

Consulte a documentação oficial do Testcontainers para Node.js e o repositório oficial testcontainers-node.

10 melhores cursos de programação em 2026

Descubra os melhores cursos de programação. Aprenda a escolher o curso ideal para iniciar ou avançar na carreira de desenvolvedor

POSTS RELACIONADOS

Ver todos

Seta para a direita