스냅샷 테스팅

스냅샷 테스팅은, 컴포넌트를 주어진 설정으로 렌더링하고, 그 결과물을 파일로 저장합니다. 그리고, 다음번에 테스팅을 진행하게 되었을때, 이전의 결과물과 일치하는지 확인합니다.

초기 렌더링 결과도 비교 할 수 있지만, 컴포넌트의 내부 메소드를 호출시키고, 다시 렌더링 시켜서 그 결과물도 스냅샷을 저장시켜서, 각 상황에 모두 이전에 렌더링했던 결과와 일치하는지 비교를 할 수 있습니다.

스냅샷 테스팅을 하기 위하여, 우선 react-test-renderer 를 설치해주어야 합니다.

$ yarn add --dev react-test-renderer

설치를 다 하셨다면, Counter.js 를 위한 테스트 코드를 작성해보겠습니다.

src/components/Counter.test.js

import React from 'react';
import renderer from 'react-test-renderer';
import Counter from './Counter';

describe('Counter', () => {
  let component = null;

  it('renders correctly', () => {
    component = renderer.create(<Counter />);
  });

  it('matches snapshot', () => {
    const tree = component.toJSON();
    expect(tree).toMatchSnapshot();
  })
});

테스트를 하게 될 때 사용되는 주요 키워드는, 다음과 같습니다:

  • describe
  • it
  • expect

우리가 코드 테스팅 로직을 쪼개고 쪼갤때, 일단 가장 작은 단위는 it 입니다. 예를 들자면:

it('is working!', () => {
  expect(something).toBeTruthy();
})

it 내부에서는 expect 를 통하여 특정 값이 우리가 예상한 값이 나왔는지 확인을 할 수 있습니다. 해당 방법은 다양한데, Jest 매뉴얼 에서 다양한 함수들을 확인해 보실 수 있습니다.

그리고 여러개의 it 을 describe 안에 넣을 수 있게 되며, describe 안에는 또 여러개의 describe 를 넣을 수 있습니다.

describe('...', () => {
  describe('...', () => {
    it('...', () => { });
    it('...', () => { });
  });
  describe('...', () => {
    it('...', () => { });
    it('...', () => { });
  });
});

describe 와 it 에서 첫번째 파라미터는 작업의 설명을 넣어주게 되는데, describe 에서는 어떤 기능을 확인하는지, 그리고 it 부분에선 무엇을 검사해야 되는지에 대한 설명을 넣으시면 됩니다.

설명을 넣을때는, 주로 영어로 작성합니다. 하지만, 영어로 작성하는 것이 익숙하지 않다면, 다음과 같이 한글로 작성해도 무방합니다:

import React from 'react';
import renderer from 'react-test-renderer';
import Counter from './Counter';

describe('Counter', () => {
  let component = null;

  it('초기 렌더링이 문제없이 되야함', () => {
    component = renderer.create(<Counter />);
  });

  it('초기 렌더링 스냅샷 일치함', () => {
    const tree = component.toJSON();
    expect(tree).toMatchSnapshot();
  })
});

이 포스트에서는 영어로 테스트 케이스 이름을 영어로 작성하고, 영어가 익숙하지 않은 분들을 위하여 설명 부분을 주석으로 한국어로 작성하겠습니다.

테스트 코드를 저장하셨다면, 자동으로 test 가 다시 작동하여 (만약에 yarn test 를 껐다면 다시 실행시키세요) 스냅샷이 생성됩니다.

스냅샷은 src/components/__snapshots__ 경로에 저장됩니다. 해당 디렉토리 내부의 Counter.test.snap 파일을 확인해보세요.

// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`Counter matches snapshot 1`] = `
<div>
  <h1>
    카운터
  </h1>
  <h2>
    1
  </h2>
  <button
    onClick={[Function]}
  >
    +
  </button>
  <button
    onClick={[Function]}
  >
    -
  </button>
</div>
`;

렌더링된 결과물이 저장되었군요!

한번, Counter.js 컴포넌트에서 기존에 <h1>카운터</h1> 라고 적혀져 있던 것을 <h1>카운터!</h1> 이런식으로 느낌표를 붙여보세요.

그러면, yarn test 를 호출한 터미널을 보시면:

위와 같이 나타나게 됩니다. 스냅샷 비교가 실패했군요.

카운터 부분이 다르다고 오류가 뜨죠? 터미널 창에서 Enter 키를 누르면 테스트를 다시 실행 할 수 있으며, U 키를 누르면, 스냅샷을 업데이트 하여 현재 스냅샷을 최신으로 설정하여 오류가 더 이상 나타나지 않게 하도록 할 수 있습니다.

초기 렌더링 스냅샷이 제대로 작동하는것을 확인했다면, 아까 h1 부분에 넣은 느낌표를 다시 지우세요.

내부 메소드 호출 및 state 조회

react-test-render 를 하면 실제로 컴포넌트가 렌더링 되기 때문에, 컴포넌트의 state 와 메소드에도 접근 할 수 있습니다.

메소드를 실행 시켜서 state 를 업데이트 시키고, 리렌더링을 하여 변화에 따라 우리가 의도한 대로 렌더링이 되는지, 스냅샷을 통하여 비교해보겠습니다.

src/components/Counter.test.js

import React from 'react';
import renderer from 'react-test-renderer';
import Counter from './Counter';

describe('Counter', () => {
  let component = null;

  it('renders correctly', () => {
    component = renderer.create(<Counter />);
  });

  it('matches snapshot', () => {
    const tree = component.toJSON();
    expect(tree).toMatchSnapshot();
  })

  // increase 가 잘 되는지 확인
  it('increases correctly', () => {
    component.getInstance().onIncrease();
    expect(component.getInstance().state.value).toBe(2); // value 값이 2인지 확인
    const tree = component.toJSON(); // re-render
    expect(tree).toMatchSnapshot(); // 스냅샷 비교
  });

  // decrease 가 잘 되는지 확인
  it('decreases correctly', () => {
    component.getInstance().onDecrease();
    expect(component.getInstance().state.value).toBe(1); // value 값이 1인지 확인
    const tree = component.toJSON(); // re-render
    expect(tree).toMatchSnapshot(); // 스냅샷 비교
  });
});

자, 이제 스냅샷을 통한 테스팅에 대한 감을 잡으셨나요? 자, 그럼 나머지 컴포넌트들도, 초기 렌더링에 해당하는 부분만, 테스트 코드를 작성해보겠습니다.

src/components/NameList.test.js

import React from 'react';
import renderer from 'react-test-renderer';
import  NameList from './NameList';

describe('NameList', () => {
  let component = null;

  it('renders correctly', () => {
    component = renderer.create(<NameList names={["벨로퍼트", "김민준"]} />);
  });

  it('matches snapshot', () => {
    const tree = component.toJSON();
    expect(tree).toMatchSnapshot();
  });
});

src/components/NameForm.test.js

import React from 'react';
import renderer from 'react-test-renderer';
import NameForm from './NameForm';

describe('NameForm', () => {
  let component = null;

  it('renders correctly', () => {
    component = renderer.create(<NameForm />);
  });

  it('matches snapshot', () => {
    const tree = component.toJSON();
    expect(tree).toMatchSnapshot();
  });
});

그리고, App.test.js 또한 새로 작성해주세요.

src/App.test.js

import React from 'react';
import renderer from 'react-test-renderer';
import  App from './App';

describe('App', () => {
  let component = null;

  it('renders correctly', () => {
    component = renderer.create(<App />);
  });

  it('matches snapshot', () => {
    const tree = component.toJSON();
    expect(tree).toMatchSnapshot();
  });
});

results matching ""

    No results matching ""