Os Snapshot Tests no Node.js permitem serializar um valor e compará-lo com uma versão aprovada armazenada em arquivo. Esse tipo de teste é útil para objetos complexos, árvores, respostas formatadas, mensagens de erro, configurações e saídas textuais que seriam trabalhosas de escrever manualmente em dezenas de asserts.
O Node.js Test Runner possui suporte nativo a snapshots. A funcionalidade foi adicionada no Node.js 22.3.0 e deixou de ser experimental no Node.js 23.4.0. Os arquivos são gerados ou atualizados com a flag --test-update-snapshots e devem ser revisados antes de entrar no controle de versão.
Neste guia, você aprenderá a criar snapshots, atualizar arquivos, usar file snapshots, personalizar serialização e caminhos, evitar dados instáveis, revisar alterações no Git, testar erros e escolher quando asserts explícitos são melhores.
O que é um snapshot test?
Um snapshot test transforma um valor em texto e compara o resultado com uma referência conhecida. A documentação oficial de snapshot testing no Node.js apresenta a API atual. Para comparações explícitas, consulte a documentação oficial do módulo Assert.
Para a base da suíte, veja Node Test Runner. Para funções simuladas, consulte Mocks no Node.js Test Runner. O artigo sobre Cobertura de Testes no Node.js ajuda a medir caminhos executados.
Primeiro snapshot
const { test } = require('node:test');
test('serializa usuário', t => {
t.assert.snapshot({
id: 1,
name: 'Ana',
active: true
});
});Na primeira execução, o teste falha se o snapshot ainda não existe. Isso evita criar referências silenciosamente sem revisão.
Gerando o arquivo
node --test --test-update-snapshotsO runner cria ou atualiza o arquivo associado ao teste. O nome padrão costuma derivar do arquivo de teste com a extensão de snapshot definida pelo Test Runner.
Executando novamente
node --testAgora o valor atual é comparado com a referência. Qualquer diferença faz o teste falhar.
Snapshots no controle de versão
Arquivos de snapshot devem ser commitados junto com os testes. Eles representam a saída aprovada e precisam passar pela mesma revisão de código.
Não adicione snapshots automaticamente sem olhar o diff. Uma atualização pode registrar um bug como novo comportamento esperado.
Revisando o diff
Ao ocorrer uma mudança:
- execute o teste sem atualizar;
- leia a diferença;
- confirme se a regra realmente mudou;
- ajuste o código ou o teste;
- atualize o snapshot conscientemente;
- revise o arquivo gerado no Git.
Vários snapshots no mesmo teste
test('gera diferentes formatos', t => {
t.assert.snapshot(formatUser(user));
t.assert.snapshot(formatOrder(order));
});Cada assert precisa de identificação estável derivada da posição e do teste. Reordenar chamadas pode alterar o mapeamento. Prefira testes separados quando os valores representam comportamentos independentes.
Snapshots de strings
test('formata relatório', t => {
const report = renderReport(data);
t.assert.snapshot(report);
});Strings multilinha, templates e mensagens formatadas são bons candidatos quando toda a saída importa.
Snapshots de objetos
Objetos grandes podem ser legíveis no arquivo, mas remova campos instáveis antes da comparação:
const normalized = {
...result,
createdAt: '<DATE>',
requestId: '<REQUEST_ID>'
};
t.assert.snapshot(normalized);Dados instáveis
Campos que costumam gerar mudanças desnecessárias incluem:
- timestamps;
- UUIDs;
- paths absolutos;
- portas aleatórias;
- ordem de propriedades não controlada;
- stack traces;
- versão do sistema operacional;
- tempo de execução.
Normalize, injete valores determinísticos ou use asserts específicos.
Relógio controlado
test('gera data previsível', t => {
t.mock.timers.enable({
apis: ['Date'],
now: new Date('2026-01-01T00:00:00Z')
});
t.assert.snapshot(createInvoice());
});A API exata de timers depende da versão. Fixe o runtime no CI.
Ordenação de arrays
Quando a ordem não faz parte do contrato, ordene antes do snapshot:
const normalized = [...users].sort(
(a, b) => a.id - b.id
);
t.assert.snapshot(normalized);Quando a ordem é importante, não normalize: o snapshot deve detectar a mudança.
Ordenação de objetos
Objetos JavaScript possuem regras de ordem, mas dados vindos de bancos ou APIs podem variar. Crie uma representação estável com as propriedades relevantes.
File snapshots
O TestContext pode oferecer fileSnapshot() para comparar um valor com um arquivo específico:
test('gera arquivo de configuração', t => {
const content = generateConfiguration();
t.assert.fileSnapshot(
content,
'./fixtures/expected-config.txt'
);
});Esse formato é útil quando o arquivo esperado também serve como fixture legível ou exemplo da documentação.
Snapshot versus file snapshot
- snapshot: runner gerencia um arquivo por teste.
- fileSnapshot: você escolhe o caminho do arquivo esperado.
Use file snapshot para artefatos completos, como HTML, SQL gerado ou configurações.
Paths seguros
Use caminhos dentro do repositório. Não permita que entrada externa determine onde o teste grava snapshots.
Personalizando o caminho
A API snapshot.setResolveSnapshotPath() permite mudar a localização:
const { snapshot } = require('node:test');
const path = require('node:path');
snapshot.setResolveSnapshotPath(testFile => {
return path.join(
path.dirname(testFile),
'__snapshots__',
`${path.basename(testFile)}.snapshot`
);
});O formato da função deve seguir a documentação da versão usada.
Configuração global
Registre a resolução antes de os snapshots serem usados. Um módulo de setup global pode centralizar a convenção em versões compatíveis.
Serializadores padrão
O Test Runner oferece configuração de serializers para transformar valores:
snapshot.setDefaultSnapshotSerializers([
value => stableSerialize(value)
]);O contrato do serializer deve ser consultado na versão atual. Ele precisa produzir saída determinística e legível.
Serializer customizado
Um serializer pode:
- ordenar propriedades;
- substituir dados variáveis;
- formatar classes;
- ocultar detalhes irrelevantes;
- reduzir objetos enormes.
Não esconda campos que fazem parte do comportamento que o teste deveria proteger.
Segredos
Snapshots ficam no repositório e em artefatos de CI. Nunca inclua:
- tokens;
- senhas;
- cookies;
- chaves privadas;
- dados pessoais;
- URLs com credenciais.
Sanitize antes de chamar o assert.
Snapshots de erros
test('formata erro público', async t => {
try {
await service.execute();
} catch (error) {
t.assert.snapshot({
name: error.name,
code: error.code,
message: error.message
});
}
});Evite stack trace, porque paths e linhas mudam com frequência. Use assert.rejects() quando apenas o tipo ou código importa.
Snapshots de HTTP
Normalize headers variáveis e não registre Authorization:
const snapshotResponse = {
status: response.status,
headers: {
'content-type': response.headers.get('content-type')
},
body: await response.json()
};
t.assert.snapshot(snapshotResponse);Snapshots de banco
Um snapshot pode validar objetos retornados, mas não substitui testes de constraints, transações e SQL real. Use banco isolado para integração.
Snapshots de HTML
HTML gerado pode ser um bom candidato se a estrutura completa é relevante. Formate a saída para reduzir diferenças de whitespace sem esconder mudanças semânticas.
Snapshots de logs
Logs contêm timestamps e IDs. Em vez de capturar tudo, teste a estrutura:
t.assert.snapshot({
level: log.level,
event: log.event,
fields: Object.keys(log.fields).sort()
});Snapshots pequenos
Um snapshot deve caber na revisão humana. Arquivos com milhares de linhas tendem a ser atualizados sem leitura. Divida o comportamento ou use asserts direcionados.
Assert explícito
Para uma regra crítica, isto é melhor:
assert.equal(result.total, 125);
assert.equal(result.currency, 'BRL');do que um snapshot do objeto inteiro, porque a intenção fica clara.
Quando usar snapshots?
- saída textual complexa;
- árvore de configuração;
- objeto com muitos campos relevantes;
- template ou serialização;
- mensagem pública padronizada;
- resultado de parser controlado.
Quando evitar?
- objeto enorme;
- dados aleatórios;
- regra com dois valores simples;
- integração que muda frequentemente;
- conteúdo com segredos;
- saída dependente de plataforma sem normalização.
Atualização em CI
O pipeline de verificação não deve executar --test-update-snapshots. Ele precisa falhar quando há diferença.
A atualização deve ocorrer localmente ou em fluxo explícito, seguida de revisão.
Script separado
{
"scripts": {
"test": "node --test",
"test:update": "node --test --test-update-snapshots"
}
}Pull requests
O revisor deve analisar código e snapshots juntos. Uma mudança de snapshot sem mudança esperada de requisito é sinal de investigação.
Merge conflicts
Snapshots podem gerar conflitos quando vários testes compartilham o mesmo arquivo. Uma organização por arquivo de teste reduz colisões.
Renomeando testes
O identificador do snapshot pode depender do nome e da posição. Renomear ou mover testes pode gerar novas entradas. Remova referências órfãs após confirmar a alteração.
Testes concorrentes
Não faça vários testes escreverem o mesmo file snapshot ao mesmo tempo. Use arquivos distintos e atualização controlada.
Compatibilidade de plataforma
Normalize:
- separadores de path;
- finais de linha;
- timezone;
- ordem de diretórios;
- mensagens específicas do sistema.
Windows e POSIX
const normalizedPath = value.replaceAll('\\', '/');Faça isso apenas quando a diferença de separador não é o comportamento testado.
Locale
Datas e números formatados podem variar conforme locale. Informe locale e timezone explicitamente.
Desempenho
Serializar objetos muito grandes torna testes lentos. Meça e reduza o valor ao contrato relevante.
Cobertura
Snapshots podem executar muitas linhas, mas um único snapshot não garante cobertura de branches. Use diferentes casos de entrada.
Mocks
Controle relógio e serviços externos para obter saída determinística. Não use mocks para reimplementar toda a integração.
Testes de mutação
Mutation testing pode revelar snapshots permissivos demais, especialmente quando o serializer remove campos importantes.
Testando serializers
O serializer customizado também precisa de testes explícitos. Um bug nele pode alterar todos os snapshots ou esconder regressões.
Erros comuns
- Atualizar sem revisar: bugs viram referência.
- Incluir timestamps: testes falham sempre.
- Snapshots gigantes: ninguém lê o diff.
- Guardar segredos: dados vazam no Git.
- Usar para regra simples: intenção fica escondida.
- Atualizar no CI: regressões são aceitas automaticamente.
- Não fixar locale: plataformas produzem saídas diferentes.
Boas práticas
- Mantenha snapshots pequenos.
- Normalize apenas instabilidade.
- Revise todo diff.
- Commit os arquivos.
- Use asserts explícitos em regras críticas.
- Separe o comando de atualização.
- Proteja segredos.
- Fixe timezone e locale.
- Teste serializers.
- Remova snapshots órfãos.
Conclusão
Os Snapshot Tests no Node.js oferecem uma forma nativa e estável de proteger saídas complexas no Test Runner. A referência é criada com --test-update-snapshots e comparada nas execuções seguintes.
Snapshots funcionam bem quando são determinísticos, pequenos e revisados. Eles não substituem asserts claros nem testes de integração. Com normalização cuidadosa, controle de segredos e atualização manual, tornam mudanças de estruturas e formatos fáceis de detectar sem transformar a suíte em uma coleção de arquivos aprovados sem leitura.



