Migawki (Snapshots)
Poznaj testy migawkowe dzięki wideo z Vue SchoolTesty migawkowe to niezwykle przydatne narzędzie, gdy chcesz mieć pewność, że wyniki Twoich funkcji nie zmieniają się nieoczekiwanie.
Podczas korzystania z migawek, Vitest tworzy migawkę danej wartości, a następnie porównuje ją z referencyjnym plikiem migawki przechowywanym obok testu. Test zakończy się niepowodzeniem, jeśli dwie migawki nie będą zgodne: oznacza to, że zmiana jest nieoczekiwana, albo referencyjna migawka musi zostać zaktualizowana do nowej wersji.
Używanie Migawek
Aby utworzyć migawkę wartości, możesz użyć toMatchSnapshot()
z API expect()
:
import { expect, it } from 'vitest';
it('toUpperCase', () => {
const result = toUpperCase('foobar');
expect(result).toMatchSnapshot();
});
Przy pierwszym uruchomieniu tego testu, Vitest tworzy plik migawki, który wygląda następująco:
// Vitest Snapshot v1, https://www.getbook.com/pl/book/vitest-1/guide/snapshot
exports['toUpperCase 1'] = '"FOOBAR"';
Artefakt migawki powinien zostać zatwierdzony wraz ze zmianami w kodzie i poddany przeglądowi w ramach procesu code review. Podczas kolejnych uruchomień testów Vitest porówna wyrenderowany wynik z poprzednią migawką. Jeśli są zgodne, test przejdzie. Jeśli nie są zgodne, oznacza to, że runner testów wykrył błąd w Twoim kodzie, który należy naprawić, albo implementacja uległa zmianie i migawka wymaga aktualizacji.
WARNING
Podczas używania migawek z asynchronicznymi testami współbieżnymi, należy użyć expect
z lokalnego kontekstu testowego, aby zapewnić prawidłowe wykrycie testu.
Migawki Wbudowane (Inline Snapshots)
Podobnie, możesz użyć toMatchInlineSnapshot()
do przechowywania migawki bezpośrednio w pliku testowym.
import { expect, it } from 'vitest';
it('toUpperCase', () => {
const result = toUpperCase('foobar');
expect(result).toMatchInlineSnapshot();
});
Zamiast tworzyć osobny plik migawki, Vitest zmodyfikuje plik testowy bezpośrednio, aby zaktualizować migawkę jako ciąg znaków:
import { expect, it } from 'vitest';
it('toUpperCase', () => {
const result = toUpperCase('foobar');
expect(result).toMatchInlineSnapshot('"FOOBAR"');
});
Pozwala to zobaczyć oczekiwany wynik bezpośrednio, bez konieczności przełączania się między różnymi plikami.
WARNING
Podczas używania migawek z asynchronicznymi testami współbieżnymi, należy użyć expect
z lokalnego kontekstu testowego, aby zapewnić prawidłowe wykrycie testu.
Aktualizowanie Migawek
Gdy otrzymana wartość nie pasuje do migawki, test kończy się niepowodzeniem i pokazuje różnicę między nimi. Gdy zmiana migawki jest oczekiwana, możesz chcieć zaktualizować migawkę do bieżącego stanu.
W trybie obserwacji możesz nacisnąć klawisz u
w terminalu, aby bezpośrednio zaktualizować niezgodną migawkę.
Możesz też użyć flagi --update
lub -u
w CLI, aby Vitest zaktualizował migawki.
vitest -u
Migawki Plikowe (File Snapshots)
Podczas wywoływania toMatchSnapshot()
, wszystkie migawki są przechowywane w sformatowanym pliku migawki. Oznacza to, że musimy escapować niektóre znaki (mianowicie podwójny cudzysłów "
i backtick `
) w ciągu migawki. Jednocześnie możesz stracić podświetlanie składni dla zawartości migawki (jeśli jest ona w jakimś języku programowania).
W związku z tym wprowadziliśmy toMatchFileSnapshot()
, aby jawnie porównywać z plikiem. Pozwala to na użycie dowolnego rozszerzenia pliku dla migawek i czyni je bardziej czytelnymi.
import { expect, it } from 'vitest';
it('render basic', async () => {
const result = renderHTML(h('div', { class: 'foo' }));
await expect(result).toMatchFileSnapshot('./test/basic.output.html');
});
Spowoduje to porównanie z zawartością pliku ./test/basic.output.html
i może zostać zaktualizowane za pomocą flagi --update
.
Migawki Obrazów (Image Snapshots)
Możliwe jest również tworzenie migawek obrazów za pomocą jest-image-snapshot
.
npm i -D jest-image-snapshot
test('image snapshot', () => {
expect(readFileSync('./test/stubs/input-image.png')).toMatchImageSnapshot();
});
Niestandardowy Serializator (Custom Serializer)
Możesz dodać własną logikę, aby zmodyfikować sposób serializacji migawek. Podobnie jak Jest, Vitest ma domyślne serializatory dla wbudowanych typów JavaScript, elementów HTML, ImmutableJS oraz dla elementów React.
Możesz jawnie dodać niestandardowy serializator za pomocą API expect.addSnapshotSerializer
.
expect.addSnapshotSerializer({
serialize(val, config, indentation, depth, refs, printer) {
// `printer` to funkcja, która serializuje wartość przy użyciu istniejących wtyczek.
return `Pretty foo: ${printer(val.foo, config, indentation, depth, refs)}`;
},
test(val) {
return val && Object.prototype.hasOwnProperty.call(val, 'foo');
},
});
Dostępna jest również opcja snapshotSerializers umożliwiająca niejawne dodawanie niestandardowych serializatorów.
import { SnapshotSerializer } from 'vitest';
export default {
serialize(val, config, indentation, depth, refs, printer) {
// `printer` to funkcja, która serializuje wartość przy użyciu istniejących wtyczek.
return `Pretty foo: ${printer(val.foo, config, indentation, depth, refs)}`;
},
test(val) {
return val && Object.prototype.hasOwnProperty.call(val, 'foo');
},
} satisfies SnapshotSerializer;
import { defineConfig } from 'vite';
export default defineConfig({
test: {
snapshotSerializers: ['path/to/custom-serializer.ts'],
},
});
Po dodaniu testu takiego jak ten:
test('foo snapshot test', () => {
const bar = {
foo: {
x: 1,
y: 2,
},
};
expect(bar).toMatchSnapshot();
});
Otrzymasz następującą migawkę:
Pretty foo: Object {
"x": 1,
"y": 2,
}
Używamy pretty-format
z biblioteki Jest do serializacji migawek. Więcej na ten temat można przeczytać tutaj: pretty-format.
Różnice w stosunku do Jest
Vitest zapewnia niemal w pełni kompatybilną funkcję migawek z Jest z kilkoma wyjątkami:
1. Nagłówek komentarza w pliku migawki jest inny {#_1-comment-header-in-the-snapshot-file-is-different}
- // Jest Snapshot v1, https://goo.gl/fbAQLP
+ // Vitest Snapshot v1, https://www.getbook.com/pl/book/vitest-1/guide/snapshot
Nie ma to wpływu na funkcjonalność, ale może wpłynąć na różnice w commitach podczas migracji z Jest.
2. printBasicPrototype
domyślnie ustawione jest na false
{#_2-printbasicprototype-is-default-to-false}
Zarówno migawki Jest, jak i Vitest są oparte na pretty-format
. W Vitest ustawiamy printBasicPrototype
domyślnie na false
, aby zapewnić czystszy wynik migawki, podczas gdy w Jest <29.0.0 jest to domyślnie true
.
import { expect, test } from 'vitest';
test('snapshot', () => {
const bar = [
{
foo: 'bar',
},
];
// w Jest
expect(bar).toMatchInlineSnapshot(`
Array [
Object {
"foo": "bar",
},
]
`);
// w Vitest
expect(bar).toMatchInlineSnapshot(`
[
{
"foo": "bar",
},
]
`);
});
Uważamy, że jest to bardziej rozsądne ustawienie domyślne dla czytelności i ogólnego doświadczenia programisty (DX). Jeśli nadal preferujesz zachowanie Jest, możesz zmienić swoją konfigurację:
// vitest.config.js
export default defineConfig({
test: {
snapshotFormat: {
printBasicPrototype: true,
},
},
});
3. Znak >
jest używany jako separator zamiast dwukropka :
dla niestandardowych wiadomości {#_3-chevron-is-used-as-a-separator-instead-of-colon-for-custom-messages}
Vitest używa znaku >
jako separatora zamiast dwukropka :
dla lepszej czytelności, gdy niestandardowa wiadomość jest przekazywana podczas tworzenia pliku migawki.
W przypadku poniższego przykładowego kodu testowego:
test('toThrowErrorMatchingSnapshot', () => {
expect(() => {
throw new Error('error');
}).toThrowErrorMatchingSnapshot('hint');
});
W Jest migawka będzie wyglądać tak:
exports[`toThrowErrorMatchingSnapshot: hint 1`] = `"error"`;
W Vitest równoważna migawka będzie wyglądać tak:
exports[`toThrowErrorMatchingSnapshot > hint 1`] = `[Error: error]`;
4. Domyślna migawka Error
jest inna dla toThrowErrorMatchingSnapshot
i toThrowErrorMatchingInlineSnapshot
{#_4-default-error-snapshot-is-different-for-tothrowerrormatchingsnapshot-and-tothrowerrormatchinginlinesnapshot}
import { expect, test } from 'vitest';
test('snapshot', () => {
// w Jest i Vitest
expect(new Error('error')).toMatchInlineSnapshot(`[Error: error]`);
// Jest tworzy migawki `Error.message` dla instancji `Error`.
// Vitest wyświetla tę samą wartość, co toMatchInlineSnapshot.
expect(() => {
throw new Error('error');
}).toThrowErrorMatchingInlineSnapshot(`"error"`);
}).toThrowErrorMatchingInlineSnapshot(`[Error: error]`);
});