Logo

Jest로 스냅샷(snapshot) 테스트하기

이번 포스팅에서는 Jest를 이용해서 스냅샷(snapshot) 테스트를 하는 방법에 대해서 알아보겠습니다.

Jest에 대한 기본적인 설치 및 사용 방법은 관련 포스팅를 참고하시길 바랍니다.

스냅샷 테스팅

스냅샷 테스팅(snapshot testing)이란 어떤 기능의 예상 결과를 미리 정확히 포착해두고 실제 결과에 비교하는 테스트 기법입니다. 테스트 대상 기능의 구현이 변경되어 실제 결과과 스냅샷을 떠놓은 예상 결과와 달라질 경우 해당 테스트 케이스는 실패하게 되는데요. 이럴 경우, 다시 새로운 스냅샷을 떠서 기존 스냅샷을 교체하는 방식으로 테스트 코드와 함께 스냅샷도 함께 유지보수를 합니다. Jest와 같은 테스팅 라이브러리를 사용하면 이러한 스냅샷 테스팅을 위한 일련의 과정을 좀 더 편하게 수행할 수 있습니다.

인라인 스냡샷 생성

먼저 별도의 파일없이 테스트 코드에 스냅샷을 삽입하는 방식인 인라인(inline) 스냅샷에 대해서 알아보도록 하겠습니다.

예를 들어, 아래와 같이 단어(word)와 횟수(times)가 주어졌을 때, 횟수만큼의 단어를 담고 있는 배열을 반환하는 함수를 테스트한다고 가정해보겠습니다.

repeat.js
function repeat(word, times = 2) {
  let words = [];
  for (let i = 0; i < times; i++) {
    words.push(word);
  }
  return words;
}

export default repeat;

Jest가 제공하는 toMatchInlineSnapshot() 함수를 이용하여 repeat() 함수에 대한 인라인 스냅샷 테스트를 한 번 작성해볼까요?

Test라는 단어와 3번의 횟수를 넘겼을 때, ["Test", "Test", "Test"]가 리턴되는지 확인합니다.

repeat.test.js
import repeat from "./repeat";

test("repeats words three times", () => {
  expect(repeat("Test", 3)).toMatchInlineSnapshot();
});

이 테스트 코드를 실행해보면 테스트 케이스가 통과하고, 하나의 스냅샷에 써졌다는 메시지가 표시될 것입니다.

$ npx jest
 PASS  src/repeat.test.js
  ✓ repeats words three times (37 ms)1 snapshot written.
Snapshot Summary
 › 1 snapshot written from 1 test suite.

Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   1 written, 1 total
Time:        7.38 s
Ran all test suites.

스냡샷이 어디에 써졌다는 걸까요❓

다시 테스트 파일을 열어보면 다음과 같이 코드가 알아서 수정된 것을 확인하실 수 있으실 겁니다❗

repeat.test.js
import repeat from "./repeat";

test("repeats words three times", () => {
  expect(repeat("Test", 3)).toMatchInlineSnapshot(`
    [
      "Test",
      "Test",
      "Test",
    ]
  `);
});

Jest가 자동으로 스냅샷을 테스트 코드에 삽입해주는 게 매우 신기합니다. 😮

인라인 스냅샷 갱신

이번에는 테스트 대상인 repeat() 함수가 배열 대신에 문자열을 반환하도록 구현을 살짝 바꿔보도록 하겠습니다.

repeat.js
function repeat(word, times = 2) {
  let words = [];
  for (let i = 0; i < times; i++) {
    words.push(word);
  }
  return words.join(); // 베열 대신 문자열 반환
}

export default repeat;

다시 테스트 코드를 실행해보면 테스트 케이스가 실패하게 되는데 다음과 같이 스냅샷과 실제 결과가 어떻게 다른지 자세하게 피드백을 줍니다.

$ npx jest
 FAIL  src/repeat.test.js
  ✕ repeats words three times (10 ms)

  ● repeats words three times

    expect(received).toMatchInlineSnapshot(snapshot)

    Snapshot name: `repeats words three times 1`

    - Snapshot  - 5
    + Received  + 1

    - [
    -   "Test",
    -   "Test",
    -   "Test",
    - ]
    + "Test,Test,Test"

      2 |
      3 | test("repeats words three times", () => {
    > 4 |   expect(repeat("Test", 3)).toMatchInlineSnapshot(`
        |                             ^
      5 |     [
      6 |       "Test",
      7 |       "Test",

      at Object.<anonymous> (src/repeat.test.js:4:29)1 snapshot failed.
Snapshot Summary
 › 1 snapshot failed from 1 test suite. Inspect your code changes or re-run jest with `-u` to update them.

Test Suites: 1 failed, 1 total
Tests:       1 failed, 1 total
Snapshots:   1 failed, 1 total
Time:        5.589 s
Ran all test suites.

-u 옵션과 함께 Jest를 재실행해주면, 테스트 코드 안에 스냅샷을 직접 수정할 필요가 없이 Jest가 알아서 수정해줍니다. 👍

$ npx jest -u
 PASS  src/repeat.test.js (7.498 s)
  ✓ repeats words three times (86 ms)1 snapshot updated.
Snapshot Summary
 › 1 snapshot updated from 1 test suite.

Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   1 updated, 1 total
Time:        11.583 s
Ran all test suites.

다시 테스트 코드를 열어보면 쉼표로 연결된 문자열을 반환되는지 테스트하도록 코드가 알아서 편집이 되어있을 것입니다.

repeat.test.js
import repeat from "./repeat";

test("repeats words three times", () => {
  expect(repeat("Test", 3)).toMatchInlineSnapshot(`"Test,Test,Test"`);
});

스냡샷 테스트를 일반 테스트로 변경하고 싶으시다면, toMatchInlineSnapshot() 함수 대신에 Jest의 다른 매처(matcher) 함수를 사용하면 됩니다.

repeat.test.js
import repeat from "./repeat";

test("repeats words three times", () => {
  expect(repeat("Test", 3)).toEqual("Test,Test,Test");
});

테스트를 작성하다보면 함수의 반환 결과를 console.log()로 한 번 콘솔에 출력해보고, 그 내용을 테스트 코드로 복사하는 경우가 있는데 상당히 귀찮습니다. 대신에 저는 개인적으로 이렇게 인라인 스냅샷을 활용해서 좀 더 편하게 테스트 코드를 작성하고 있습니다.

한가짐 주의할 점은 인라인 스냅샷은 내부적으로 Prettier 코드 포맷터를 사용하고 있습니다. 따라서 자신의 프로젝트의 Prettier가 아직 설치가 안되어 있다면 Jest와 별도로 설치를 해주셔야 합니다.

$ npm i -D prettier

코드 포맷터 Prettier에 대한 좀 더 자세한 내용은 관련 포스팅를 참고하시길 바랍니다.

파일 스냅샷 생성

스냅샷이 길거나 복잡한 경우에는 테스트 파일 안에 스냅샷을 함께 두는 것보다는 별도의 파일에 관리하는 것이 용이합니다.

Jest에서는 파일 스냅샷 테스팅을 지원하기 위해서 toMatchSnapshot()이라는 함수를 제공하고 있습니다.

repeat.test.js
import repeat from "./repeat";

test("repeats words three times", () => {
  expect(repeat("Test", 3)).toMatchSnapshot();
});

toMatchSnapshot() 함수를 사용해서 테스트 코드를 수정 후 실행해보겠습니다.

$ npx jest
 PASS  src/repeat.test.js
  ✓ repeats words three times (6 ms)1 snapshot written.
Snapshot Summary
 › 1 snapshot written from 1 test suite.

Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   1 written, 1 total
Time:        6.589 s
Ran all test suites.

테스트 파일이 위치하는 경로에 __snapshots__ 디렉토리가 생성되고, 그 안에 repeat.test.js.snap 파일이 생길 것입니다.

repeat.test.js.snap
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`repeats words three times 1`] = `"Test,Test,Test"`;

파일 스냅샷 갱신

이 번에는 테스트 대상인 repeat() 함수가 문자열 대신에 배열을 반환하는 원래 구현으로 다시 되돌려놓겠습니다.

repeat.js
function repeat(word, times = 2) {
  let words = [];
  for (let i = 0; i < times; i++) {
    words.push(word);
  }
  return words; // 문자열 대신 배열 반환
}

export default repeat;

다시 테스트 코드를 실행해보면 실제 결과가 스냅샷이 달려져서 테스트가 케이스 실패할 것입니다.

$ npx jest
 PASS  src/repeat.test.js
  ✓ repeats words three times (6 ms)

Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   1 passed, 1 total
Time:        5.699 s
Ran all test suites.
 npx jest
 FAIL  src/repeat.test.js
  ✕ repeats words three times (8 ms)

  ● repeats words three times

    expect(received).toMatchSnapshot()

    Snapshot name: `repeats words three times 1`

    - Snapshot  - 1
    + Received  + 5

    - "Test,Test,Test"
    + [
    +   "Test",
    +   "Test",
    +   "Test",
    + ]

      2 |
      3 | test("repeats words three times", () => {
    > 4 |   expect(repeat("Test", 3)).toMatchSnapshot()
        |                             ^
      5 |   // expect(repeat("Test", 3)).toEqual("Test,Test,Test");
      6 | });
      7 |

      at Object.<anonymous> (src/repeat.test.js:4:29)1 snapshot failed.
Snapshot Summary
 › 1 snapshot failed from 1 test suite. Inspect your code changes or re-run jest with `-u` to update them.

Test Suites: 1 failed, 1 total
Tests:       1 failed, 1 total
Snapshots:   1 failed, 1 total
Time:        5.979 s
Ran all test suites.

인라인 스냅샷 테스팅할 때와 동일하게 -u 옵션을 줘서 Jest를 실행하면 다시 스냅샷이 생성됩니다.

$ npx jest -u
 PASS  src/repeat.test.js
  ✓ repeats words three times (7 ms)1 snapshot updated.
Snapshot Summary
 › 1 snapshot updated from 1 test suite.

Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   1 updated, 1 total
Time:        6.379 s
Ran all test suites.

__snapshots__/repeat.test.js.snap 파일을 열어보면 스냅샷이 변경된 코드 기준으로 자동으로 편집되어 있는 것이 확인될 것입니다.

repeat.test.js.snap
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`repeats words three times 1`] = `
[
  "Test",
  "Test",
  "Test",
]
`;

전체 코드

본 포스팅에서 작성한 코드는 아래에서 확인하시고 바로 실행해보실 수 있습니다.

마치면서

이상으로 Jest를 이용하여 어떻게 인라인 스냅샷과 파일 스냅샷 테스팅을 할 수 있는지에 대해서 살펴보았습니다. 스냅샷 테스팅은 어떤 함수의 예상 결과를 직접 코딩하지 않아도 되는 편리함이 있지만, 무분별하게 사용할 경우 스냅샷 자체의 유지보수가 어려워지는 부작용도 있습니다. 따라서, 테스트의 예상 결과를 개발자가 직접 작성하는데 시간과 노력이 많이 들어가는 경우에만 제한적으로 사용하시기를 권장드립니다.

Jest에 연관된 포스팅은 Jest 태그를 통해서 쉽게 만나보세요!