Teste de snapshot salva a saída de um valor e compara com execuções de teste futuras. Isso é particularmente útil para componentes de UI, objetos complexos ou qualquer saída que precise permanecer consistente.
Snapshots Básicos
Testes de snapshot são escritos usando o matcher .toMatchSnapshot():
import { test, expect } from "bun:test";
test("snap", () => {
expect("foo").toMatchSnapshot();
});A primeira vez que este teste é executado, o argumento para expect será serializado e escrito em um arquivo de snapshot especial em um diretório __snapshots__ ao lado do arquivo de teste.
Arquivos de Snapshot
Após executar o teste acima, o Bun criará:
seu-projeto/
├── snap.test.ts
└── __snapshots__/
└── snap.test.ts.snapO arquivo de snapshot contém:
// Bun Snapshot v1, https://bun.com/docs/test/snapshots
exports[`snap 1`] = `"foo"`;Em execuções futuras, o argumento é comparado com o snapshot em disco.
Atualizando Snapshots
Snapshots podem ser regenerados com o seguinte comando:
bun test --update-snapshotsIsso é útil quando:
- Você mudou intencionalmente a saída
- Está adicionando novos testes de snapshot
- A saída esperada mudou legitimamente
Snapshots Inline
Para valores menores, você pode usar snapshots inline com .toMatchInlineSnapshot(). Estes snapshots são armazenados diretamente no seu arquivo de teste:
import { test, expect } from "bun:test";
test("snapshot inline", () => {
// Primeira execução: snapshot será inserido automaticamente
expect({ hello: "world" }).toMatchInlineSnapshot();
});Após a primeira execução, o Bun atualiza automaticamente seu arquivo de teste:
import { test, expect } from "bun:test";
test("snapshot inline", () => {
expect({ hello: "world" }).toMatchInlineSnapshot(`
{
"hello": "world",
}
`);
});Usando Snapshots Inline
- Escreva seu teste com
.toMatchInlineSnapshot() - Execute o teste uma vez
- O Bun atualiza automaticamente seu arquivo de teste com o snapshot
- Nas execuções subsequentes, o valor será comparado com o snapshot inline
Snapshots inline são particularmente úteis para valores pequenos e simples onde é útil ver a saída esperada diretamente no arquivo de teste.
Snapshots de Erro
Você também pode snapshotar mensagens de erro usando .toThrowErrorMatchingSnapshot() e .toThrowErrorMatchingInlineSnapshot():
import { test, expect } from "bun:test";
test("snapshot de erro", () => {
expect(() => {
throw new Error("Algo deu errado");
}).toThrowErrorMatchingSnapshot();
expect(() => {
throw new Error("Outro erro");
}).toThrowErrorMatchingInlineSnapshot();
});Após executar, a versão inline se torna:
test("snapshot de erro", () => {
expect(() => {
throw new Error("Algo deu errado");
}).toThrowErrorMatchingSnapshot();
expect(() => {
throw new Error("Outro erro");
}).toThrowErrorMatchingInlineSnapshot(`"Outro erro"`);
});Uso Avançado de Snapshots
Objetos Complexos
Snapshots funcionam bem com objetos aninhados complexos:
import { test, expect } from "bun:test";
test("snapshot de objeto complexo", () => {
const user = {
id: 1,
name: "John Doe",
email: "john@example.com",
profile: {
age: 30,
preferences: {
theme: "dark",
notifications: true,
},
},
tags: ["developer", "javascript", "bun"],
};
expect(user).toMatchSnapshot();
});Snapshots de Array
Arrays também são adequados para teste de snapshot:
import { test, expect } from "bun:test";
test("snapshot de array", () => {
const numbers = [1, 2, 3, 4, 5].map(n => n * 2);
expect(numbers).toMatchSnapshot();
});Snapshots de Saída de Função
Snapshot da saída de funções:
import { test, expect } from "bun:test";
function generateReport(data: any[]) {
return {
total: data.length,
summary: data.map(item => ({ id: item.id, name: item.name })),
timestamp: "2024-01-01", // Fixo para teste
};
}
test("geração de relatório", () => {
const data = [
{ id: 1, name: "Alice", age: 30 },
{ id: 2, name: "Bob", age: 25 },
];
expect(generateReport(data)).toMatchSnapshot();
});Snapshots de Componentes React
Snapshots são particularmente úteis para componentes React:
import { test, expect } from "bun:test";
import { render } from "@testing-library/react";
function Button({ children, variant = "primary" }) {
return <button className={`btn btn-${variant}`}>{children}</button>;
}
test("snapshots de componente Button", () => {
const { container: primary } = render(<Button>Clique em mim</Button>);
const { container: secondary } = render(<Button variant="secondary">Cancelar</Button>);
expect(primary.innerHTML).toMatchSnapshot();
expect(secondary.innerHTML).toMatchSnapshot();
});Property Matchers
Para valores que mudam entre execuções de teste (como timestamps ou IDs), use property matchers:
import { test, expect } from "bun:test";
test("snapshot com valores dinâmicos", () => {
const user = {
id: Math.random(), // Isso muda a cada execução
name: "John",
createdAt: new Date().toISOString(), // Isso também muda
};
expect(user).toMatchSnapshot({
id: expect.any(Number),
createdAt: expect.any(String),
});
});O snapshot armazenará:
exports[`snapshot com valores dinâmicos 1`] = `
{
"createdAt": Any<String>,
"id": Any<Number>,
"name": "John",
}
`;Serializadores Personalizados
Você pode personalizar como objetos são serializados em snapshots:
import { test, expect } from "bun:test";
// Serializador personalizado para objetos Date
expect.addSnapshotSerializer({
test: val => val instanceof Date,
serialize: val => `"${val.toISOString()}"`,
});
test("serializador personalizado", () => {
const event = {
name: "Reunião",
date: new Date("2024-01-01T10:00:00Z"),
};
expect(event).toMatchSnapshot();
});Melhores Práticas
Mantenha Snapshots Pequenos
// Bom: Snapshots focados
test("formatação de nome de usuário", () => {
const formatted = formatUserName("john", "doe");
expect(formatted).toMatchInlineSnapshot(`"John Doe"`);
});
// Evite: Snapshots enormes difíceis de revisar
test("renderização de página inteira", () => {
const page = renderEntirePage();
expect(page).toMatchSnapshot(); // Isso poderia ser milhares de linhas
});Use Nomes de Teste Descritivos
// Bom: Claro o que o snapshot representa
test("formata moeda com símbolo USD", () => {
expect(formatCurrency(99.99)).toMatchInlineSnapshot(`"$99.99"`);
});
// Evite: Não claro o que está sendo testado
test("teste de formatação", () => {
expect(format(99.99)).toMatchInlineSnapshot(`"$99.99"`);
});Agrupe Snapshots Relacionados
import { describe, test, expect } from "bun:test";
describe("componente Button", () => {
test("variante primary", () => {
expect(render(<Button variant="primary">Clique</Button>))
.toMatchSnapshot();
});
test("variante secondary", () => {
expect(render(<Button variant="secondary">Cancelar</Button>))
.toMatchSnapshot();
});
test("estado disabled", () => {
expect(render(<Button disabled>Disabled</Button>))
.toMatchSnapshot();
});
});Lide com Dados Dinâmicos
// Bom: Normalizar dados dinâmicos
test("formato de resposta de API", () => {
const response = {
data: { id: 1, name: "Test" },
timestamp: Date.now(),
requestId: generateId(),
};
expect({
...response,
timestamp: "TIMESTAMP",
requestId: "REQUEST_ID",
}).toMatchSnapshot();
});
// Ou use property matchers
test("resposta de API com matchers", () => {
const response = getApiResponse();
expect(response).toMatchSnapshot({
timestamp: expect.any(Number),
requestId: expect.any(String),
});
});Gerenciando Snapshots
Revisando Mudanças de Snapshot
Quando snapshots mudam, revise-os cuidadosamente:
# Ver o que mudou
git diff __snapshots__/
# Atualizar se mudanças são intencionais
bun test --update-snapshots
# Commitar snapshots atualizados
git add __snapshots__/
git commit -m "Atualizar snapshots após mudanças de UI"Limpando Snapshots Não Utilizados
O Bun alertará sobre snapshots não utilizados:
Aviso: 1 snapshot não utilizado encontrado:
my-test.test.ts.snap: "teste antigo que não existe mais 1"Remova snapshots não utilizados deletando-os dos arquivos de snapshot ou executando testes com flags de limpeza se disponíveis.
Organizando Arquivos de Snapshot Grandes
Para projetos grandes, considere organizar testes para manter arquivos de snapshot gerenciáveis:
tests/
├── components/
│ ├── Button.test.tsx
│ └── __snapshots__/
│ └── Button.test.tsx.snap
├── utils/
│ ├── formatters.test.ts
│ └── __snapshots__/
│ └── formatters.test.ts.snapSolução de Problemas
Falhas de Snapshot
Quando snapshots falham, você verá um diff:
- Esperado
+ Recebido
Object {
- "name": "John",
+ "name": "Jane",
}Causas comuns:
- Mudanças intencionais (atualize com
--update-snapshots) - Mudanças não intencionais (corrija o código)
- Dados dinâmicos (use property matchers)
- Diferenças de ambiente (normalize os dados)
Diferenças de Plataforma
Esteja ciente de diferenças específicas de plataforma:
// Caminhos podem diferir entre Windows/Unix
test("operações de arquivo", () => {
const result = processFile("./test.txt");
expect({
...result,
path: result.path.replace(/\\/g, "/"), // Normalizar caminhos
}).toMatchSnapshot();
});