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

Testcontainers no Node.js

Atualizado em: 5 de setembro de 2026

Terminal Linux usado em desenvolvimento e administração de aplicações Node.js

Usar Testcontainers no Node.js permite executar testes de integração contra PostgreSQL, MongoDB, Redis, Kafka, RabbitMQ, Elasticsearch e outros serviços reais iniciados em containers descartáveis. Em vez de simular o comportamento de uma dependência complexa, o teste sobe uma instância isolada, aplica configuração e a remove ao terminar.

Essa abordagem detecta problemas que mocks não reproduzem: SQL inválido, índices ausentes, diferenças de versão, transações, encoding, timeouts, autenticação e comportamento do protocolo. O custo é um ambiente de teste mais pesado, que precisa de runtime de containers, estratégias de espera e limpeza confiável.

Neste guia, você aprenderá instalação, GenericContainer, módulos, wait strategies, redes, fixtures, migrations, paralelismo, CI, reutilização, logs, segurança e boas práticas.

O que é Testcontainers?

Testcontainers é uma biblioteca para iniciar dependências reais durante testes. A documentação oficial do Testcontainers para Node.js apresenta containers genéricos, imagens, networking, Docker Compose, wait strategies e módulos para bancos e brokers. A documentação do projeto também descreve runtimes de container suportados e configuração.

Para estruturar testes nativos, consulte Node Test Runner. Para ambientes de banco versionados, veja Migrações de Banco no Node.js.

Instalação

npm install --save-dev testcontainers

Módulos específicos podem ser instalados separadamente, conforme a versão atual da biblioteca.

Requisitos

O ambiente precisa de Docker ou outro runtime compatível. O usuário do CI deve conseguir criar containers, redes e volumes.

Primeiro container

import { GenericContainer } from 'testcontainers';

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

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

// executar testes

await container.stop();

O host e a porta mapeada não devem ser presumidos. Testcontainers escolhe recursos livres.

Cleanup com try/finally

const container = await startDependency();

try {
  await runTests(container);
} finally {
  await container.stop();
}

Mesmo quando o teste falha, o container precisa ser removido.

Hooks do runner

let postgres;

before(async () => {
  postgres = await startPostgres();
});

after(async () => {
  await postgres.stop();
});

Use o hook global do arquivo ou suíte para reduzir tempo de startup.

PostgreSQL module

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

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

const connectionString = postgres.getConnectionUri();

Fixe uma versão de imagem compatível com produção, não use latest.

Aplicando migrations

await migrate({
  databaseUrl: postgres.getConnectionUri()
});

Os testes devem criar o schema da mesma forma que produção. Isso verifica que todas as migrations funcionam em banco vazio.

Seeds

Insira apenas dados necessários para cada suíte:

await seedCustomer(db, {
  id: 'customer-1',
  plan: 'premium'
});

Fixtures pequenas tornam falhas compreensíveis.

Banco limpo por teste

Opções:

  • transaction rollback;
  • truncate entre testes;
  • schema separado;
  • database separado;
  • container por suíte;
  • container por teste para isolamento máximo.

Escolha equilíbrio entre velocidade e independência.

Rollback de transação

Funciona quando o código usa a mesma conexão e não testa commits reais. Para filas, conexões múltiplas e transações independentes, truncate ou banco separado é mais confiável.

Container por suíte

Subir uma instância e limpar dados entre casos costuma ser eficiente. Se testes alteram configuração global, use instâncias separadas.

Wait strategies

Container iniciado não significa serviço pronto. Use estratégia de espera:

import { Wait } from 'testcontainers';

const container = await new GenericContainer(image)
  .withExposedPorts(8080)
  .withWaitStrategy(
    Wait.forHttp('/health', 8080)
      .forStatusCode(200)
  )
  .start();

Logs, portas e health endpoints também podem indicar readiness.

Evite sleep fixo

await new Promise(resolve => setTimeout(resolve, 5000));

Esse padrão deixa testes lentos e flakey. Espere uma condição real.

Timeout de startup

.withStartupTimeout(60_000)

Ajuste para imagem e ambiente do CI. Falhe com logs úteis quando exceder.

Variáveis de ambiente

const api = await new GenericContainer('my-api:test')
  .withEnvironment({
    DATABASE_URL: postgres.getConnectionUri(),
    NODE_ENV: 'test'
  });

Não use credenciais de produção.

Arquivos e configuração

É possível copiar conteúdo ou montar arquivos, conforme o runtime. Prefira imagens reproduzíveis e configuração pequena.

Networking

import { Network } from 'testcontainers';

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

const database = await new GenericContainer('postgres:18-alpine')
  .withNetwork(network)
  .withNetworkAliases('database')
  .start();

const api = await new GenericContainer('my-api:test')
  .withNetwork(network)
  .withEnvironment({
    DATABASE_HOST: 'database'
  })
  .start();

Containers na mesma rede usam aliases, não portas do host.

Host versus rede

O processo de teste no host usa getHost() e getMappedPort(). Outro container usa alias e porta interna.

Docker Compose

Quando a aplicação depende de vários serviços definidos em Compose, Testcontainers pode iniciar o conjunto. Ainda assim, controle imagens, healthchecks e limpeza.

Redis

import { RedisContainer } from '@testcontainers/redis';

const redis = await new RedisContainer(
  'redis:8-alpine'
).start();

Teste scripts Lua, TTL, Streams e comportamento real. Consulte Redis Streams no Node.js.

MongoDB

Use o módulo MongoDB e uma topologia compatível quando testar transactions ou change streams. Uma instância standalone não reproduz replica set.

Consulte MongoDB no Node.js.

Kafka e RabbitMQ

Brokers precisam de readiness e configuração corretas. Teste publish, consumo, redelivery, idempotência e dead-letter, não apenas conexão.

Elasticsearch

Subir Elasticsearch pode exigir mais memória. Defina limites e use imagem compatível com o cliente.

Consulte Elasticsearch no Node.js.

ToxiProxy

O módulo ToxiProxy permite injetar latência, desconexão e perda de conexão para testar resiliência.

await proxy.setConnectionCut(true);

A API exata depende da versão do módulo.

Testando timeout

Adicione latência acima do limite do cliente e confirme:

  • erro classificado;
  • cancelamento;
  • retry limitado;
  • circuit breaker;
  • liberação de recursos.

Testando reconexão

Pare e reinicie a dependência ou corte a rede. Confirme que o cliente volta sem multiplicar listeners ou perder estado indevidamente.

Imagens customizadas

Você pode construir uma imagem para testes:

import { GenericContainer, ImageFromDockerfile } from 'testcontainers';

const image = await ImageFromDockerfile()
  .withDockerfileFromBuilder(builder)
  .build();

Evite builds pesados em cada suíte. Use cache do CI.

Imagens privadas

O ambiente precisa de autenticação no registry. Nunca escreva token em logs.

Logs do container

const stream = await container.logs();

stream.on('data', line => {
  logger.debug({
    container: 'database',
    line: line.toString()
  });
});

Anexe logs apenas em falha para reduzir ruído.

Paralelismo

Testes paralelos podem disputar:

  • CPU;
  • memória;
  • pull de imagens;
  • conexões;
  • nomes de resources;
  • limites do runner.

Use portas dinâmicas e IDs únicos.

Limite de concorrência

Não execute cinquenta clusters pesados no mesmo runner. Separe suítes e configure workers de acordo com recursos.

Reutilização

Algumas configurações permitem reutilizar containers localmente. Isso acelera desenvolvimento, mas pode preservar estado. Em CI, prefira isolamento e limpeza previsível.

Global setup

Um setup global pode iniciar dependências uma vez e compartilhar dados de conexão. Garanta que o teardown execute e que testes não dependam de ordem.

CI com Docker

O runner precisa acessar o daemon. Em ambientes containerizados, opções incluem socket montado, Docker-in-Docker ou serviço remoto. Cada abordagem possui implicações de segurança.

Segurança do daemon

Acesso ao Docker socket equivale a privilégios elevados no host. Use runners isolados, efêmeros e controlados.

Pull de imagens

Faça cache ou pre-pull de imagens frequentes. Fixe tags e, para maior reprodutibilidade, digests.

Multi-arch

Desenvolvedores ARM e CI x86 podem usar imagens diferentes. Escolha imagens multi-arch ou configure plataforma conscientemente.

Testes de repositories

Execute a mesma suíte de contrato contra o adapter real:

  • salvar e recuperar;
  • constraints;
  • transações;
  • paginação;
  • locks;
  • tipos;
  • migrations.

Consulte Repository Pattern no Node.js.

Testes de integração HTTP

Suba apenas dependências externas e execute a aplicação no processo do teste. Isso facilita coverage e debugging.

Teste end-to-end em container

Para validar imagem final, suba API, banco e broker em rede comum. Execute requests pelo host e confirme healthcheck e shutdown.

Fixtures determinísticas

Use datas e IDs fixos. Não dependa de dados previamente existentes.

Relógio

Para TTL e tarefas temporais, injete Clock quando possível. Testcontainers não torna o relógio do sistema automaticamente controlável.

Performance dos testes

Meça:

  • tempo de pull;
  • startup;
  • migrations;
  • execução;
  • cleanup.

Otimize o maior componente, não suponha que o container é o gargalo.

Separando níveis

  • unitários: rápidos e sem container;
  • integração: adapter com dependência real;
  • end-to-end: aplicação e infraestrutura;
  • resiliência: falhas e rede;
  • carga: ferramentas específicas.

Não transforme todo teste em end-to-end.

Observabilidade em falha

Quando uma suíte falhar, preserve:

  • logs do container;
  • versão da imagem;
  • porta e rede;
  • resultado de healthcheck;
  • migrations aplicadas;
  • tempo de startup.

Teste flakey

Não repita automaticamente até passar sem investigar. Procure race condition, readiness incorreta, isolamento insuficiente e recursos saturados.

Erros comuns

  • Usar latest: resultados mudam sem alteração no código.
  • Sleep fixo: testes ficam lentos e instáveis.
  • Porta fixa: paralelismo quebra.
  • Sem teardown: recursos acumulam.
  • Estado compartilhado: testes dependem de ordem.
  • Mockar o adapter real: bugs de integração passam.
  • CI sem recursos: containers falham aleatoriamente.
  • Socket exposto: risco de segurança aumenta.

Boas práticas

  • Fixe versões de imagens.
  • Use wait strategies.
  • Mapeie portas dinamicamente.
  • Aplique migrations reais.
  • Limpe dados entre testes.
  • Limite paralelismo.
  • Capture logs em falha.
  • Use runners isolados.
  • Teste resiliência.
  • Mantenha unitários rápidos.

Conclusão

Usar Testcontainers no Node.js aproxima os testes do comportamento real de bancos, brokers e serviços. Containers descartáveis revelam problemas de protocolo, configuração, transação e versão que mocks não cobrem.

Com imagens fixas, wait strategies, limpeza, migrations e CI dimensionado, a suíte permanece reproduzível. Testcontainers deve complementar testes unitários, concentrando-se nas fronteiras em que infraestrutura real muda o resultado.

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