Test con Instantánea
Los tests de instantánea son de gran utilidad cuando se quiere asegurar que la UI no cambia 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. The test will fail if the two snapshots do not match: either the change is unexpected, or the reference snapshot needs to be updated to the new version of the UI component.
Comprobación Instantánea con Jest
Se puede tomar un enfoque similar cuando se trata de comprobar tus component de React. En lugar de presentar la interfaz gráfica de usuario, lo cual requeriría construir la app en su totalidad, se puede usar un renderer de tests para rápidamente generar un valor serializable para tu árbol de 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>
`;
El artefacto de la instantánea debe ser incluído (commit) con los cambios en el código, y revisado como parte de la revisión de código. Jest uses pretty-format to make snapshots human-readable during code review. On subsequent test runs, Jest will compare the rendered output with the previous snapshot. Si coinciden, pasará el test. 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.
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
. 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.
Puede encontrarse más información sobre cómo funciona la comprobación de instantánea y por qué la hemos construido en el post de lanzamiento del blog. Recomendamos leer este post del blog para hacerse la idea de cuando se debería usar comprobación de instantánea. We also recommend watching this egghead video on Snapshot Testing with Jest.
Actualizando Instantáneas
Es muy fácil detectar cuando una prueba de instantánea falla después de introducir un bug. Cuando eso ocurra, siga adelante y arregle el problema, y asegúrese de que sus tests de instantánea se ejecutan con éxito otra vez. A continuación vamos a hablar sobre el caso de cuando un test de instantánea está fallando debido a un cambio intencionado en la implementación.
Tal situación puede presentarse si intencionalmente cambiamos la dirección del enlace del componente Enlace en nuestro ejemplo.
// estuche de prueba actualizado con un Link a dirección distinta
it('renders correctly', () => {
const tree = renderer
.create(<Link page="http://www.instagram.com">Instagram</Link>)
.toJSON();
expect(tree).toMatchSnapshot();
});
En ese caso, Jest imprimirá esta salida:
Ya que sólo actualizamos nuestro componente para que apunte a una dirección diferente, es razonable esperar cambios en la instantánea de este componente. Nuestro test de instantánea está fallando porque la instantánea de nuestro componente actualizado ya no concuerda con el artefacto de instantáneas para este caso de test.
Para resolver esto, necesitamos actualizar nuestros artefactos de instantánea. Puede ejecutar Jest con un "flag" que le dirá que vuelva a generar las instantáneas:
jest --updateSnapshot
Prosiga y acepte los cambios ejecutando el comando anterior. También puede utilizar equivalente carácter único "flag" -u
para volver a generar instantáneas si lo prefiere. Esto volverá a generar los artefactos de instantánea para todos los tests de instantánea fallidos. Si tuviéramos cualquier test de instantánea fallido adicional debido a un bug no intencionado, necesitaríamos arreglar el "bug" antes de volver a generar instantáneas para impedir grabar instantáneas con un comportamiento defectuoso.
Si se desea limitar que casos de test instantáneos se vuelven a generar, se puede pasar un "flag" adicional: --nombreTestPatrón
para volver a grabar instantáneas sólo para esos tests que coinciden con el patrón.
You can try out this functionality by cloning the snapshot example, modifying the Link
component, and running Jest.
Interactive Snapshot Mode
Failed snapshots can also be updated interactively in watch mode:
Once you enter Interactive Snapshot Mode, Jest will step you through the failed snapshots one test at a time and give you the opportunity to review the failed output.
From here you can choose to update that snapshot or skip to the next:
Once you're finished, Jest will give you a summary before returning back to watch mode:
Inline Snapshots
Inline snapshots behave identically to external snapshots (.snap
files), except the snapshot values are written automatically back into the source code. This means you can get the benefits of automatically generated snapshots without having to switch to an external file to make sure the correct value was written.
Ejemplo:
First, you write a test, calling .toMatchInlineSnapshot()
with no arguments:
it('renders correctly', () => {
const tree = renderer
.create(<Link page="https://example.com">Example Site</Link>)
.toJSON();
expect(tree).toMatchInlineSnapshot();
});
The next time you run Jest, tree
will be evaluated, and a snapshot will be written as an argument to 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>
`);
});
That's all there is to it! You can even update the snapshots with --updateSnapshot
or using the u
key in --watch
mode.
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",
}
`;
Any given value that is not a matcher will be checked exactly and saved to the snapshot:
it('will check the values and pass', () => {
const user = {
createdAt: new Date(),
name: 'Bond... James Bond',
};
expect(user).toMatchSnapshot({
createdAt: expect.any(Date),
name: 'Bond... James Bond',
});
});
// Snapshot
exports[`will check the values and pass 1`] = `
{
"createdAt": Any<Date>,
"name": 'Bond... James Bond',
}
`;
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
Los tests deben ser deterministas. Running the same tests multiple times on a component that has not changed should produce the same results every time. Usted es responsable de asegurar que sus instantáneas generadas no incluyen información específica sobre la plataforma o cualquier otra información que no sea determinista.
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. En este caso se puede create un "mock" del método Date.now() para devolver un valor constante cada vez que la prueba se ejecuta:
Date.now = jest.fn(() => 1_482_363_367_071);
Cada vez que se ejecuta el test de prueba instantánea, Date.now()
devuelve 1482363367071
consistentemente. Esto resultará en la misma instantánea generada para este componente independientemente de cuando se ejecute el test.
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>
`;
A:
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`;
Preguntas Frecuentes
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
. Se espera que todas las instantáneas sean parte del código que se ejecuta en el CI, y ya que las nuevas instantáneas pasan automáticamente, no deberían pasar un test ejecutado en un sistema de CI. Se recomienda hacer siempre un "commit" de todas las instantáneas para mantenerlas en el control de versiones.
¿Deben los archivos de instantáneas ser incluidos en "commits"?
Sí, todos los archivos de instantánea deben esta en "commits" junto a los módulos que están cubriendo y sus tests. They should be considered part of a test, similar to the value of any other assertion in Jest. De hecho, las instantáneas representan el estado de los módulos en cualquier momento dado en el tiempo. De esta manera, cuando se modifican los módulos, Jest puede saber lo que cambiado desde la versión anterior. También puede proporcionar mucho contexto adicional durante la revisión del código en la cual los revisores pueden estudiar los cambios mucho mejor.
¿Los tests de instantánea solo funcionan con components de React?
Los components de React y React Native son un buen caso de uso para tests de instantánea. Sin embargo, las instantáneas pueden capturar cualquier valor serializable y puede usarse en cualquier momento en que el objetivo sea comprobar si el resultado es correcto. El repositorio de Jest contiene muchos ejemplos del "output" de Jest en sí mismo, el "output" de la librería de afirmación de Jest, así como mensajes de "log" de varias partes de la base de código de Jest. See an example of snapshotting CLI output in the Jest repo.
¿Cuál es la diferencia entre tests de instantánea y tests de regresión visual?
Tests de instantánea y tests de regresión visual son dos maneras distintas de testear interfaces de usuario, y cumplen dos objetivos diferentes. Las herramientas de tests de regresión visual toman capturas de pantalla de páginas web y comparan las imágenes resultantes pixel por pixel. With Snapshot testing values are serialized, stored within text files, and compared using a diff algorithm. There are different trade-offs to consider and we listed the reasons why snapshot testing was built in the Jest blog.
Does snapshot testing replace unit testing?
Los tests de instantánea per-se es sólo una de las más de 20 afirmaciones que están incluidas en Jest. The aim of snapshot testing is not to replace existing unit tests, but to provide additional value and make testing painless. En algunos escenarios, los tests de instantánea pueden potencialmente eliminar la necesidad de tests unitarios para un conjunto particular de funcionalidades (por ejemplo componentes de React), pero también pueden coexistir.
¿Cuál es el rendimiento de tests de instantáneas en cuanto a velocidad y tamaño de los archivos generados?
Jest ha sido reescrito pensando en rendimiento, y tests de instantánea no es una excepción. Ya que las instantáneas se almacenan en archivos de texto, esta forma de prueba es rápida y fiable. Jest genera un nuevo archivo por cada archivo de test que invoca al marcador 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.
¿Cómo resuelvo conflictos dentro de archivos de instantáneas?
Los archivos de instantáneas siempre deben representar el estado actual de los módulos que están cubriendo. Therefore, if you are merging two branches and encounter a conflict in the snapshot files, you can either resolve the conflict manually or update the snapshot file by running Jest and inspecting the result.
¿Es posible aplicar los principios de desarrollo orientado a pruebas con tests de instantánea?
Aunque es posible escribir archivos de instantáneas manualmente, generalmente no es accesible. Snapshots help to figure out whether the output of the modules covered by tests is changed, rather than giving guidance to design the code in the first place.
Does code coverage work with snapshot testing?
Yes, as well as with any other test.