Снапшот-тестирование
Изучите снапшоты по видео от Vue SchoolСнапшот-тесты — это очень полезный инструмент, позволяющий убедиться, что вывод ваших функций не изменяется неожиданно.
При использовании снапшотов Vitest создает снапшот заданного значения, а затем сравнивает его с эталонным файлом снапшота, хранящимся рядом с тестом. Тест завершится ошибкой, если два снапшота не совпадают: это может означать либо неожиданное изменение, либо необходимость обновления эталонного снапшота до новой версии результата.
Использование снапшотов
Чтобы создать снапшот значения, вы можете использовать toMatchSnapshot()
из API expect()
:
import { expect, it } from 'vitest';
it('toUpperCase', () => {
const result = toUpperCase('foobar');
expect(result).toMatchSnapshot();
});
При первом запуске этого теста Vitest создает файл снапшота, который выглядит следующим образом:
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports['toUpperCase 1'] = '"FOOBAR"';
Артефакт снапшота должен быть зафиксирован в системе контроля версий вместе с изменениями кода и просмотрен в рамках процесса проверки кода. При последующих запусках тестов Vitest будет сравнивать текущий вывод с предыдущим снапшотом. Если они совпадают, тест пройдет. Если они не совпадают, это означает, что либо средство запуска тестов обнаружило ошибку в вашем коде, которую следует исправить, либо реализация изменилась, и снапшот необходимо обновить.
WARNING
При использовании снапшотов с асинхронными параллельными тестами необходимо использовать expect
из локального контекста теста, чтобы убедиться, что обнаружен правильный тест.
Встроенные снапшоты
Аналогично, вы можете использовать toMatchInlineSnapshot()
для хранения снапшота непосредственно в файле теста.
import { expect, it } from 'vitest';
it('toUpperCase', () => {
const result = toUpperCase('foobar');
expect(result).toMatchInlineSnapshot();
});
Вместо создания отдельного файла снапшота Vitest напрямую изменит файл теста, чтобы обновить снапшот в виде строки:
import { expect, it } from 'vitest';
it('toUpperCase', () => {
const result = toUpperCase('foobar');
expect(result).toMatchInlineSnapshot('"FOOBAR"');
});
Это позволяет вам видеть ожидаемый вывод напрямую, не переключаясь между разными файлами.
WARNING
При использовании снапшотов с асинхронными параллельными тестами необходимо использовать expect
из локального контекста теста, чтобы убедиться, что обнаружен правильный тест.
Обновление снапшотов
Когда полученное значение не совпадает со снапшотом, тест завершается ошибкой и показывает вам разницу между ними. Если изменение снапшота ожидается, вы можете обновить снапшот до текущего состояния.
В режиме наблюдения вы можете нажать клавишу u
в терминале, чтобы напрямую обновить провалившийся снапшот.
Или вы можете использовать флаг --update
или -u
в CLI, чтобы Vitest обновил снапшоты.
vitest -u
Файловые снапшоты
При вызове toMatchSnapshot()
все снапшоты сохраняются в специальном файле с расширением .snap
. Это означает, что нам нужно экранировать некоторые символы (а именно двойную кавычку "
и обратную кавычку `
) в строке снапшота. При этом вы можете потерять подсветку синтаксиса для содержимого снапшота (если оно написано на каком-либо языке).
В свете этого мы ввели toMatchFileSnapshot()
для явного сопоставления с файлом. Это позволяет вам присваивать любое расширение файла файлу снапшота и делает его более читабельным.
import { expect, it } from 'vitest';
it('render basic', async () => {
const result = renderHTML(h('div', { class: 'foo' }));
await expect(result).toMatchFileSnapshot('./test/basic.output.html');
});
Он будет сравниваться с содержимым ./test/basic.output.html
. И может быть перезаписан с флагом --update
.
Снапшоты изображений
Также возможно делать снапшоты изображений с помощью jest-image-snapshot
.
npm i -D jest-image-snapshot
test('image snapshot', () => {
expect(readFileSync('./test/stubs/input-image.png')).toMatchImageSnapshot();
});
Пользовательский сериализатор
Вы можете добавить свою собственную логику для изменения способа сериализации ваших снапшотов. Как и Jest, Vitest имеет сериализаторы по умолчанию для встроенных типов JavaScript, HTML-элементов, ImmutableJS и для элементов React.
Вы можете явно добавить пользовательский сериализатор, используя API expect.addSnapshotSerializer
.
expect.addSnapshotSerializer({
serialize(val, config, indentation, depth, refs, printer) {
// printer — это функция, которая сериализует значение с использованием существующих плагинов.
return `Pretty foo: ${printer(val.foo, config, indentation, depth, refs)}`;
},
test(val) {
return val && Object.prototype.hasOwnProperty.call(val, 'foo');
},
});
Мы также поддерживаем опцию snapshotSerializers для неявного добавления пользовательских сериализаторов.
import { SnapshotSerializer } from 'vitest';
export default {
serialize(val, config, indentation, depth, refs, printer) {
// printer — это функция, которая сериализует значение с использованием существующих плагинов.
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 'vitest/config';
export default defineConfig({
test: {
snapshotSerializers: ['path/to/custom-serializer.ts'],
},
});
После добавления такого теста:
test('foo snapshot test', () => {
const bar = {
foo: {
x: 1,
y: 2,
},
};
expect(bar).toMatchSnapshot();
});
Вы получите следующий снапшот:
Pretty foo: Object {
"x": 1,
"y": 2,
}
Мы используем pretty-format
из Jest для сериализации снапшотов. Вы можете прочитать об этом подробнее здесь: pretty-format.
Отличия от Jest
Vitest предоставляет почти совместимую функцию снапшотов с Jest с несколькими исключениями:
1. Заголовок комментария в файле снапшота отличается {#_1-comment-header-in-the-snapshot-file-is-different}
- // Jest Snapshot v1, https://goo.gl/fbAQLP
+ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
Это не влияет на функциональность, но может привести к различиям в коммите при миграции с Jest.
2. printBasicPrototype
по умолчанию false
{#_2-printbasicprototype-is-default-to-false}
Снапшоты Jest и Vitest основаны на pretty-format
. В Vitest мы устанавливаем printBasicPrototype
по умолчанию в false
, чтобы обеспечить более чистый вывод снапшотов, тогда как в Jest <29.0.0 этот параметр по умолчанию true
.
import { expect, test } from 'vitest';
test('snapshot', () => {
const bar = [
{
foo: 'bar',
},
];
// в Jest
expect(bar).toMatchInlineSnapshot(`
Array [
Object {
"foo": "bar",
},
]
`);
// в Vitest
expect(bar).toMatchInlineSnapshot(`
[
{
"foo": "bar",
},
]
`);
});
Мы считаем, что это более разумное значение по умолчанию для удобочитаемости и удобства работы разработчиков в целом. Если вы все еще предпочитаете поведение Jest, вы можете изменить свою конфигурацию:
import { defineConfig } from 'vitest/config';
export default defineConfig({
test: {
snapshotFormat: {
printBasicPrototype: true,
},
},
});
3. Шеврон >
используется в качестве разделителя вместо двоеточия :
для пользовательских сообщений {#_3-chevron-is-used-as-a-separator-instead-of-colon-for-custom-messages}
Vitest использует шеврон >
в качестве разделителя вместо двоеточия :
для лучшей читаемости при передаче пользовательского сообщения во время создания файла снапшота.
Для следующего примера тестового кода:
test('toThrowErrorMatchingSnapshot', () => {
expect(() => {
throw new Error('error');
}).toThrowErrorMatchingSnapshot('hint');
});
В Jest снапшот будет:
exports[`toThrowErrorMatchingSnapshot: hint 1`] = `"error"`;
В Vitest эквивалентный снапшот будет:
exports[`toThrowErrorMatchingSnapshot > hint 1`] = `[Error: error]`;
4. Снапшот Error
по умолчанию отличается для toThrowErrorMatchingSnapshot
и toThrowErrorMatchingInlineSnapshot
{#_4-default-error-snapshot-is-different-for-tothrowerrormatchingsnapshot-and-tothrowerrormatchinginlinesnapshot}
import { expect, test } from 'vitest'
test('snapshot', () => {
// в Jest и Vitest
expect(new Error('error')).toMatchInlineSnapshot(`[Error: error]`)
// Jest создает снапшот `Error.message` для экземпляра `Error`
// Vitest выводит то же значение, что и при использовании toMatchInlineSnapshot
expect(() => {
throw new Error('error')
}).toThrowErrorMatchingInlineSnapshot(`"error"`)
}).toThrowErrorMatchingInlineSnapshot(`[Error: error]`)
})