Snapshot Testing
Testes de snapshot são ferramentas bem úteis sempre que você desejar garantir que sua UI não seja alterada inesperadamente.
A typical snapshot test case renders a UI component, takes a snapshot, then compares it to a reference snapshot file stored alongside the test. 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. Considere este exemplo de teste para um simples componente de Link:
import React from 'react';
import renderer from 'react-test-renderer';
import Link from '../Link.react';
it('renders correctly', () => {
const tree = renderer
.create(<Link page="http://www.facebook.com">Facebook</Link>)
.toJSON();
expect(tree).toMatchSnapshot();
});
A primeira vez que esse teste é executado, Jest cria um arquivo de snapshot que se parece com isto:
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 usa pretty-format para fazer snapshots legíveis durante a revisão do código. On subsequent test runs, Jest will compare the rendered output with the previous snapshot. Se eles coincidirem, o teste passará. If they don't match, either the test runner found a bug in your code (in the <Link>
component in this case) that should be fixed, or the implementation has changed and the snapshot needs to be updated.
Note: The snapshot is directly scoped to the data you render – in our example the
<Link />
component withpage
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 theLink.react.js
. Also, rendering the same component with different props in other snapshot tests will not affect the first one, as the tests don't know about each other.
Mais informações sobre como testes de snapshot funcionam e por que o 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.
To resolve this, we will need to update our snapshot artifacts. You can run Jest with a flag that will tell it to re-generate 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.
Você pode experimentar essa funcionalidade por clonar o exemplo de snapshot, modificando o componente Link
e executando 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 no 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.
Inline snapshots are powered by Prettier. To use inline snapshots you must have
prettier
installed in your project. Your Prettier configuration will be respected when writing to test files.If you have
prettier
installed in a location where Jest can't find it, you can tell Jest how to find it using the"prettierPath"
configuration property.
Exemplo:
Primeiro, você escreve um teste, chamando .toMatchInlineSnapshot()
sem argumentos:
it('renderiza corretamente', () => {
const tree = renderer
.create(<Link page="https://prettier.io">Prettier</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('renderiza corretamente', () => {
const tree = renderer
.create(<Link page="https://prettier.io">Prettier</Link>)
.toJSON();
expect(tree).toMatchInlineSnapshot();
});
That's all there is to it! You can even update the snapshots with --updateSnapshot
or using the u
key in --watch
mode.
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`] = `
Object {
"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`] = `
Object {
"createdAt": Any<Date>,
"id": Any<Number>,
"name": "LeBron James",
}
`;
Qualquer valor dado que não combina será verificado exatamente e salvo no snapshot:
it('verificará os valores e passar', () => {
const user = {
createdAt: new Date(),
name: 'Bond... James Bond',
};
expect(user).toMatchSnapshot({
createdAt: expect.any(Date),
name: 'Bond... James Bond',
});
});
// Snapshot
exports[`verificará os valores e passar 1`] = `
Object {
"createdAt": Any<Date>,
"name": 'Bond... James Bond',
}
`;
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.
O objetivo é facilitar a revisão de snapshots em pull requests, e lutar contra o hábito de regenerar snapshots quando a suíte de testes falhar, em vez de analisar as causas profundas do seu fracasso.
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.
Por exemplo, se você tem um componente Clock que usa Date.now()
, o snapshot gerado a partir deste componente será diferente cada vez que o caso de teste é executado. Neste caso podemos simular o método Date.now() para retornar um valor consistente toda vez que o teste é executado:
Date.now = jest.fn(() => 1482363367071);
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>
`;
Como a parte posterior descreve exatamente o que se espera na saída, é mais claro ver quando está errado:
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?
React and React Native components are a good use case for snapshot testing. 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. Veja um exemplo de saída em linha de comando de teste de snapshot no repositório do Jest.
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.