Skip to main content
Versão: Próximo

Teste de Snapshot

Testes de snapshot são ferramentas bem úteis sempre que você desejar garantir que sua UI não seja alterada inesperadamente.

Um caso comum de teste de snapshot é renderizar um componente de UI, obter uma captura disso, então compará-lo para com uma imagem de referência armazenada com o teste. O teste irá falhar se as duas imagens não coincidirem: quer a mudança seja inesperada, ou a captura de tela precisa ser atualizada para a nova versão do componente da UI.

Testes de Snapshot com Jest

Uma abordagem semelhante pode ser tomada quando se trata de testar seus componentes React. Em vez de renderizar a interface gráfica do usuário, ou UI, que iria precisar construir o aplicativo inteiro, você pode usar um renderizador de teste para gerar rapidamente um valor serializável para sua árvore React. Consider this example test for a Link component:

import renderer from 'react-test-renderer';
import Link from '../Link';

it('renders correctly', () => {
const tree = renderer
.create(<Link page="http://www.facebook.com">Facebook</Link>)
.toJSON();
expect(tree).toMatchSnapshot();
});

The first time this test is run, Jest creates a snapshot file that looks like this:

exports[`renders correctly 1`] = `
<a
className="normal"
href="http://www.facebook.com"
onMouseEnter={[Function]}
onMouseLeave={[Function]}
>
Facebook
</a>
`;

O artefato do snapshot deve ser comitado (committed, em inglês) junto com as alterações de código, e revisado como parte de seu processo de revisão de código. Jest uses pretty-format to make snapshots human-readable during code review. Nas próximas vezes em que o teste for executado, o Jest irá comparar a saída renderizada com o snapshot gerado anteriormente. Se coincidirem, o teste passará. Se não coinciderem, isso pode significar que o test runner achou um bug no seu código (neste caso, no componente <Link>) que deve ser resolvido ou pode significar que a implementação mudou e o snapshot precisa ser atualizado.

note

The snapshot is directly scoped to the data you render – in our example the <Link> component with page prop passed to it. This implies that even if any other file has missing props (say, App.js) in the <Link> component, it will still pass the test as the test doesn't know the usage of <Link> component and it's scoped only to the Link.js. Da mesma maneira, renderizar o mesmo componente com propriedades diferentes em outros testes de snapshots não afetará o primeiro teste, já que os testes não se conhecem.

informação

Mais informações sobre como testes de snapshot funcionam e por que os construímos podem ser encontradas no post de lançamento no blog. Nós recomendamos ler este post no blog para obter uma boa base de quando você deve usar teste de snapshot. Também recomendamos assistir este vídeo no egghead sobre Testes de Snapshot com Jest.

Atualizando Snapshots

É simples de detectar quando um snapshot falhar depois que um bug foi introduzido. Quando isso acontecer, vá em frente e corrija o problema e certifique-se de que seus testes de snapshot estão passando novamente. Agora, vamos falar sobre o caso de quando um teste de snapshot está falhando devido a uma alteração intencional da implementação.

Uma situação assim pode ocorrer se nós intencionalmente mudarmos o endereço que para onde o componente Link aponta em no nosso exemplo.

// Caso de teste atualizado com um Link para um endereço diferente
it('renderiza corretamente', () => {
const tree = renderer
.create(<Link page="http://www.instagram.com">Instagram</Link>)
.toJSON();
expect(tree).toMatchSnapshot();
});

Nesse caso, Jest irá imprimir esta saída:

Uma vez que acabamos de atualizar nosso componente para apontar para um endereço diferente, é razoável esperar mudanças no snapshot para este componente. Nosso caso de teste de snapshot está falhando porque o snapshot para nosso componente atualizado já não coincide com o artefato de snapshot para este caso de teste.

Para resolver esse problema, precisamos atualizar nossos artefatos de snapshot. Você pode executar Jest com uma flag que irá dizer para re-gerar snapshots:

jest --updateSnapshot

Vá em frente e aceite as alterações executando o comando acima. Você também pode usar o caractere único equivalente -u como flag para re-gerar snapshots, se preferir. Isso irá re-gerar artefatos de snapshot para todos testes de snapshot que falharam. Se tivéssemos mais algum teste de snapshot falhando devido a um bug não intencional, precisamos corrigir o bug antes de re-gerar snapshots para evitar a gravação de snapshot do bug.

Se você gostaria de limitar quais casos de teste snapshot devem ser gerados novamente, você pode passar uma flag adicional --testNamePattern para re-gravar snapshots somente para aqueles testes que correspondem ao padrão.

You can try out this functionality by cloning the snapshot example, modifying the Link component, and running Jest.

Modo de Snapshot Interativo

Os snapshots que falharam também podem ser atualizados de forma interativa no modo "watch":

Uma vez que você entra o Modo de Snapshot Interativo, Jest irá passo a passo nos snapshots com falhas, um conjunto de teste por vez, e dar-lhe a oportunidade de revisar o resultado que falhou.

A partir daqui você pode optar por atualizar esse snapshot ou pular para o próximo:

Uma vez terminado, Jest irá fornecer-lhe um resumo antes de retornar para o modo "watch":

Inline Snapshots

Inline snapshots se comportam de forma idêntica aos snapshots externos (arquivos .snap), exceto os valores de snapshot que são escritos automaticamente no código fonte. Isso significa que você pode obter os benefícios de snapshots gerados automaticamente sem precisar alternar para um arquivo externo para certificar-se de que o valor correto foi escrito.

Example:

Primeiro, você escreve um teste, chamando .toMatchInlineSnapshot() sem argumentos:

it('renders correctly', () => {
const tree = renderer
.create(<Link page="https://example.com">Example Site</Link>)
.toJSON();
expect(tree).toMatchInlineSnapshot();
});

Na próxima vez que você executar o Jest, tree será calculado, e um snapshot será escrito como um argumento para toMatchInlineSnapshot:

it('renders correctly', () => {
const tree = renderer
.create(<Link page="https://example.com">Example Site</Link>)
.toJSON();
expect(tree).toMatchInlineSnapshot(`
<a
className="normal"
href="https://example.com"
onMouseEnter={[Function]}
onMouseLeave={[Function]}
>
Example Site
</a>
`);
});

É tudo o que há nisso! Você pode até mesmo atualizar os snapshots com --updateSnapshot ou usando a chave u no modo --watch.

By default, Jest handles the writing of snapshots into your source code. However, if you're using prettier in your project, Jest will detect this and delegate the work to prettier instead (including honoring your configuration).

Property Matchers

Often there are fields in the object you want to snapshot which are generated (like IDs and Dates). If you try to snapshot these objects, they will force the snapshot to fail on every run:

it('will fail every time', () => {
const user = {
createdAt: new Date(),
id: Math.floor(Math.random() * 20),
name: 'LeBron James',
};

expect(user).toMatchSnapshot();
});

// Snapshot
exports[`will fail every time 1`] = `
{
"createdAt": 2018-05-19T23:36:09.816Z,
"id": 3,
"name": "LeBron James",
}
`;

For these cases, Jest allows providing an asymmetric matcher for any property. These matchers are checked before the snapshot is written or tested, and then saved to the snapshot file instead of the received value:

it('will check the matchers and pass', () => {
const user = {
createdAt: new Date(),
id: Math.floor(Math.random() * 20),
name: 'LeBron James',
};

expect(user).toMatchSnapshot({
createdAt: expect.any(Date),
id: expect.any(Number),
});
});

// Snapshot
exports[`will check the matchers and pass 1`] = `
{
"createdAt": Any<Date>,
"id": Any<Number>,
"name": "LeBron James",
}
`;

Qualquer valor dado que não combina será verificado exatamente e salvo no snapshot:

it('will check the matchers and pass', () => {
const user = {
createdAt: new Date(),
id: Math.floor(Math.random() * 20),
name: 'LeBron James',
};

expect(user).toMatchSnapshot({
createdAt: expect.any(Date),
id: expect.any(Number),
});
});

// Snapshot
exports[`will check the matchers and pass 1`] = ` James Bond',
});
});

// Snapshot
exports[`will check the values and pass 1`] = `
{
"createdAt": Any<Date>,
"name": 'Bond... "LeBron James",
}
`;
tip

If the case concerns a string not an object then you need to replace random part of that string on your own before testing the snapshot.
You can use for that e.g. replace() and regular expressions.

const randomNumber = Math.round(Math.random() * 100);
const stringWithRandomData = `<div id="${randomNumber}">Lorem ipsum</div>`;
const stringWithConstantData = stringWithRandomData.replace(/id="\d+"/, 123);
expect(stringWithConstantData).toMatchSnapshot();

Other ways this can be done is using the snapshot serializer or mocking the library responsible for generating the random part of the code you're snapshotting.

Best Practices

Snapshots are a fantastic tool for identifying unexpected interface changes within your application – whether that interface is an API response, UI, logs, or error messages. As with any testing strategy, there are some best-practices you should be aware of, and guidelines you should follow, in order to use them effectively.

1. Treat snapshots as code

Commit snapshots and review them as part of your regular code review process. This means treating snapshots as you would any other type of test or code in your project.

Ensure that your snapshots are readable by keeping them focused, short, and by using tools that enforce these stylistic conventions.

As mentioned previously, Jest uses pretty-format to make snapshots human-readable, but you may find it useful to introduce additional tools, like eslint-plugin-jest with its no-large-snapshots option, or snapshot-diff with its component snapshot comparison feature, to promote committing short, focused assertions.

The goal is to make it easy to review snapshots in pull requests, and fight against the habit of regenerating snapshots when test suites fail instead of examining the root causes of their failure.

2. Tests should be deterministic

Seus testes devem ser determinísticos. Running the same tests multiple times on a component that has not changed should produce the same results every time. Você é responsável por certificar-se de que seus snapshots gerados não incluem dados específicos de plataforma ou outros não determinísticos.

For example, if you have a Clock component that uses Date.now(), the snapshot generated from this component will be different every time the test case is run. Neste caso podemos simular o método Date.now() para retornar um valor consistente toda vez que o teste é executado:

Date.now = jest.fn(() => 1_482_363_367_071);

Agora, toda vez que o caso de teste de snapshot é executado, Date.now() retornará consistentemente 1482363367071. Isso resultará no mesmo snapshot sendo gerado para este componente independentemente de quando o teste é executado.

3. Use descriptive snapshot names

Always strive to use descriptive test and/or snapshot names for snapshots. The best names describe the expected snapshot content. This makes it easier for reviewers to verify the snapshots during review, and for anyone to know whether or not an outdated snapshot is the correct behavior before updating.

For example, compare:

exports[`<UserName /> should handle some test case`] = `null`;

exports[`<UserName /> should handle some other test case`] = `
<div>
Alan Turing
</div>
`;

Para:

exports[`<UserName /> should render null`] = `null`;

exports[`<UserName /> should render Alan Turing`] = `
<div>
Alan Turing
</div>
`;

Since the latter describes exactly what's expected in the output, it's more clear to see when it's wrong:

exports[`<UserName /> should render null`] = `
<div>
Alan Turing
</div>
`;

exports[`<UserName /> should render Alan Turing`] = `null`;

Perguntas Frequentes

Are snapshots written automatically on Continuous Integration (CI) systems?

No, as of Jest 20, snapshots in Jest are not automatically written when Jest is run in a CI system without explicitly passing --updateSnapshot. Espera-se que todos os snapshots são parte do código que é executado em CI e como novos snapshots passam automaticamente, eles não devem passar um teste executado em um sistema de CI. É recomendável sempre dar commit em todos os snapshots e mantê-los no controle de versão.

Arquivos de snapshot devem ser comitados (committed, em inglês)?

Sim, todos os arquivos de snapshot devem ser comitados juntamente com os módulos que eles cobrem e seus testes. Eles devem ser considerados parte de um teste, similar ao valor de qualquer outra verificação em Jest. Na verdade, snapshots representam o estado dos módulos fonte em qualquer ponto no tempo. Desta forma, quando os módulos fonte são modificados, Jest pode dizer o que mudou da versão anterior. Também pode fornecer um monte de contexto adicional durante a revisão do código na qual os revisores podem estudar melhor as alterações.

Teste de snapshot só funciona com componentes React?

Componentes React e React Native são um bom caso de uso para testes de snapshot. No entanto, snapshots podem capturar qualquer valor que pode ser serializado e devem ser usados sempre que o objetivo é testar se a saída é correta. O repositório Jest contém muitos exemplos de testes da saída do próprio Jest, a saída da biblioteca de verificação do Jest, bem como mensagens de log de várias partes do código do Jest. See an example of snapshotting CLI output in the Jest repo.

Qual é a diferença entre teste de snapshot e teste de regressão visual?

Teste de snapshot e teste de regressão visual são duas maneiras distintas de testar interfaces de usuário, ou UIs, e eles servem para finalidades diferentes. Ferramentas de teste de regressão visual tiram screenshots de páginas da web e comparam as imagens resultantes pixel por pixel. Com testes de snapshot os valores são serializados, armazenados dentro de arquivos de texto e comparados usando um algoritmo de comparação. There are different trade-offs to consider and we listed the reasons why snapshot testing was built in the Jest blog.

Teste de snapshot substitui teste unitário?

Teste de snapshot é apenas um das mais de 20 verificações que acompanham Jest. O objetivo do teste de snapshot não é substituir os testes unitários existentes, mas fornecer valor adicional e tornar os testes mais fáceis. Em alguns cenários, teste de snapshot podem potencialmente eliminar a necessidade de testes unitários para um determinado conjunto de funcionalidades (por exemplo, componentes React), mas também podem trabalhar juntos.

Qual é o desempenho do teste de snapshot no que diz respeito a velocidade e tamanho dos arquivos gerados?

Jest foi reescrito tendo desempenho em mente, e teste de snapshot não é uma exceção. Como os snapshots são armazenados dentro de arquivos de texto, esta forma de teste é rápida e confiável. Jest gera um novo arquivo para cada arquivo de teste que invoca o matcher toMatchSnapshot. The size of the snapshots is pretty small: For reference, the size of all snapshot files in the Jest codebase itself is less than 300 KB.

Como eu resolvo conflitos dentro de arquivos de snapshot?

Arquivos de snapshot sempre devem representar o estado atual dos módulos que estão cobrindo. Portanto, se você estiver dando merge em duas branches e encontra um conflito nos arquivos de snapshot, você pode resolver o conflito manualmente ou atualizar o arquivo de snapshot executando Jest e inspecionar o resultado.

É possível aplicar os princípios de desenvolvimento orientado a testes com teste de snapshot?

Embora seja possível escrever arquivos de snapshot manualmente, isto geralmente não é amigável. Snapshots ajudam a descobrir se a saída dos módulos cobertos por testes foi alterada, ao invés de dar orientação para a concepção do código.

Cobertura de código funciona com testes de snapshot?

Sim, assim como com qualquer outro teste.