스냅샷
Vue School 비디오로 스냅샷 배우기스냅샷 테스트는 함수의 출력이 예상치 않게 변경되지 않도록 보장하는 데 매우 유용한 도구입니다.
스냅샷을 사용하면 Vitest는 주어진 값의 스냅샷을 찍어 테스트와 함께 저장된 참조 스냅샷 파일과 비교합니다. 두 스냅샷이 일치하지 않으면 테스트가 실패합니다. 이는 변경이 예상치 못한 것일 수도 있고, 참조 스냅샷을 새로운 결과에 맞춰 업데이트해야 할 수도 있음을 의미합니다.
스냅샷 사용
값의 스냅샷을 찍으려면 expect() API의 toMatchSnapshot()를 사용할 수 있습니다.
import { expect, it } from 'vitest';
it('toUpperCase', () => {
const result = toUpperCase('foobar');
expect(result).toMatchSnapshot();
});이 테스트가 처음 실행될 때 Vitest는 다음과 같이 스냅샷 파일을 생성합니다.
// Vitest Snapshot v1, https://www.getbook.com/ko/book/vitest-1/guide/snapshot
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 키를 눌러 실패한 스냅샷을 업데이트할 수 있습니다.
또는 CLI에서 --update 또는 -u 플래그를 사용하여 Vitest가 스냅샷을 업데이트하도록 지시할 수 있습니다.
vitest -u파일 스냅샷
toMatchSnapshot()를 호출하면 모든 스냅샷이 형식화된 스냅 파일에 저장됩니다. 즉, 스냅샷 문자열 내의 일부 문자(특히 큰따옴표 " 및 백틱 `)를 이스케이프 처리해야 합니다. 한편, 스냅샷 콘텐츠(특정 언어로 작성된 경우)에 대한 구문 강조 표시가 손실될 수 있습니다.
이러한 문제를 고려하여 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-snapshottest('image snapshot', () => {
expect(readFileSync('./test/stubs/input-image.png')).toMatchImageSnapshot();
});사용자 정의 직렬 변환기
스냅샷이 직렬화되는 방식을 변경하기 위해 사용자 정의 로직을 추가할 수 있습니다. Jest와 마찬가지로 Vitest는 내장 JavaScript 타입, HTML 요소, ImmutableJS 및 React 요소에 대한 기본 직렬 변환기를 제공합니다.
expect.addSnapshotSerializer API를 통해 사용자 정의 직렬 변환기를 명시적으로 추가할 수 있습니다.
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 'vite';
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,
}스냅샷 직렬화에는 Jest의 pretty-format을 활용합니다. 자세한 내용은 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://www.getbook.com/ko/book/vitest-1/guide/snapshot이는 기능에 실질적인 영향을 미치지는 않지만, Jest에서 마이그레이션할 때 커밋 diff에 영향을 줄 수 있습니다.
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",
},
]
`);
});이는 가독성과 전반적인 개발자 경험(DX)에 더 합리적인 기본값이라고 판단됩니다. 여전히 Jest의 동작을 선호한다면 설정을 수정할 수 있습니다.
// vitest.config.js
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. toThrowErrorMatchingSnapshot 및 toThrowErrorMatchingInlineSnapshot에 대한 기본 Error 스냅샷이 다릅니다 {#_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` 인스턴스에 대해 `Error.message`를 스냅샷으로 저장합니다.
// Vitest는 toMatchInlineSnapshot과 동일한 값을 표시합니다.
expect(() => {
throw new Error('error');
}).toThrowErrorMatchingInlineSnapshot(`"error"`);
}).toThrowErrorMatchingInlineSnapshot(`[Error: error]`);
});