스냅샷
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://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
키를 눌러 실패한 스냅샷을 직접 업데이트할 수 있습니다.
또는 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-snapshot
test('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 '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,
}
스냅샷 직렬화에는 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://vitest.dev/guide/snapshot.html
이것은 기능에 실제로 영향을 미치지 않지만 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 방식이 더 선호된다면 설정을 변경할 수 있습니다.
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. 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]`)
})