3. 카운터 테스팅

카운터는, 숫자가 올라가고, 내려가죠. 정말로 간단한 로직입니다.

액션 생성 함수 테스팅

우선, 테스트 파일을 생성하고, 액션 생성 함수들이 모두 제대로 작동하는지 확인해줍니다.

src/store/modules/counter.test.js

import counter, * as counterActions from './counter';

describe('counter', () => {
  describe('actions', () => {
    it('should create actions', () => {
      const expectedActions =[
        { type: 'counter/INCREASE' },
        { type: 'counter/DECREASE' },
      ];
      const actions = [
        counterActions.increase(),
        counterActions.decrease(),
      ];
      expect(actions).toEqual(expectedActions);
    });
  });
});

리듀서 함수와, 액션 생성 함수를 import 해온 다음에, 우선 액션 생성 함수부터 검증해주었습니다. 여기서 하단의 toEqual 의 경우엔, expect 에 전달해준 객체와 actions 와, 우측의 expectedActions 객체의 내부 값들이 모두 일치하는지를 확인해줍니다.

리듀서 테스팅

그럼, 리듀서도 테스팅 해줍시다. 리듀서의 경우엔, 초기 상태 설정이 잘 되어있는지 확인하고, 각 액션이 디스패치 됐을 때의 결과물을 검증합니다.

src/store/modules/conter.test.js

import counter, * as counterActions from './counter';

describe('counter', () => {
  describe('actions', () => {
    it('should create actions', () => {
      const expectedActions =[
        { type: 'counter/INCREASE' },
        { type: 'counter/DECREASE' },
      ];
      const actions = [
        counterActions.increase(),
        counterActions.decrease(),
      ];
      expect(actions).toEqual(expectedActions);
    });
  });
  describe('reducer', () => {
    let state = counter(undefined, {});
    it('should return the initialState', () => {
      expect(state).toHaveProperty('number', 0);
    });

    it('should increase', () => {
      state = counter(state, counterActions.increase());
      expect(state).toHaveProperty('number', 1);
    });

    it('should decrease', () => {
      state = counter(state, counterActions.decrease());
      expect(state).toHaveProperty('number', 0);
    });
  })
});

프리젠테이셔널 컴포넌트 테스팅

이제는 Counter.js 컴포넌트를 테스팅 해주겠습니다. 이미 스냅샷 테스팅은 되어있는데요, props 가 전달 됐을 때, 정말 잘 나타나는지 한번 더 검증을 해주겠습니다.

src/components/Counter.test.js

import React from 'react';
import { shallow } from 'enzyme';
import Counter from './Counter';

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

  it('renders correctly', () => {
    component = shallow(<Counter value={700}/>);
  });

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

  it('is 700', () => {
    expect(component.find('h2').at(0).text(), '700');
  })
});

value props 를 700 으로 건네주고, 렌더링 결과물의 h2 안에 700이 있는지 검증했습니다.

두번째로 테스팅 해주어햐 하는것은, props 로 전달해줄 onIncrease 와 onDecrease 가 제대로 작동하는지 입니다.

우리가 함수를 임의로 따로 만들어서 전달해준다음에 확인할 수도 있겠지만, Jest 에는 함수가 호출됐는지 확인하기 위한 fn 이라는 도구가 있는데 매우 유용합니다.

src/components/Counter.test.js

import React from 'react';
import { shallow } from 'enzyme';
import Counter from './Counter';

describe('Counter', () => {
  let component = null;
  const mockIncrease = jest.fn();
  const mockDecrease = jest.fn();

  it('renders correctly', () => {
    component = shallow(
      <Counter value={700} onIncrease={mockIncrease} onDecrease={mockDecrease}/>
    );
  });

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

  it('is 700', () => {
    expect(component.find('h2').at(0).text(), '700');
  });

  it('calls functions', () => {
    const buttons = component.find('button');
    buttons.at(0).simulate('click');
    buttons.at(1).simulate('click');
    expect(mockIncrease.mock.calls.length).toBe(1);
    expect(mockDecrease.mock.calls.length).toBe(1);
  });
});

각 버튼을 클릭하면, mockIncrease 와 mockDecrease 가 호출 될 것이고, 해당 함수는 호출이 되면 함수명.mock.calls.length) 의 값이 1씽 올라가게 된답니다.

컨테이너

이제 컨테이너 컴포넌트의 테스트 코드를 작성해봅시다. 우선, 이를 진행하기 위해선 redux-mock-store 를 설치해주어야 합니다. 이 라이브러리는, 가짜 스토어를 만들어서, 특정 액션이 디스패치됐는지 안됐는지 판별하는것을 쉽게 해줍니다.

$ yarn add redux-mock-store

그 다음에는, 컨테이너 컴포넌트를 렌더링해주겠습니다.

src/containers/CounterContainer.js

import React from 'react';
import { mount } from 'enzyme';
import CounterContainer from './CounterContainer';
import configureMockStore from 'redux-mock-store';
import * as counterActions from '../store/modules/counter';

describe('CounterContainer', () => {
  let component = null;
  let buttons = null;
  const mockStore = configureMockStore();

  // 데이터들을 받아올 가짜 스토어 만들기
  let store = mockStore({
    counter: {
      number: 0
    }
  });

  it('renders properly', () => {
    const context = { store };
    component = mount(<CounterContainer />, { context });
    // 혹은 component = mount(<CounterContainer store={store} />);
  });

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

컨테이너 컴포넌트를 렌더링 할 때에는, 를 사용하는 대신, 위 처럼 context 에 store 를 넣어주거나, props 에 store 를 넣어주시면 됩니다.

그 다음에는, 각 버튼 클릭을 시뮬레이트 해보고, 액션이 잘 디스패치 됐는지 확인해보겠습니다.

src/containers/CounterContainer.js

import React from 'react';
import { mount } from 'enzyme';
import CounterContainer from './CounterContainer';
import configureMockStore from 'redux-mock-store';
import * as counterActions from '../store/modules/counter';

describe('CounterContainer', () => {
  let component = null;
  let buttons = null;
  const mockStore = configureMockStore();

  // 데이터들을 받아올 가짜 스토어 만들기
  let store = mockStore({
    counter: {
      number: 0
    }
  });

  it('renders properly', () => {
    const context = { store };
    component = mount(<CounterContainer />, { context });
    // 혹은 component = mount(<CounterContainer store={store} />);
  });

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

  it('dispatches INCREASE action', () => {
    component.find('button').at(0).simulate('click');
    expect(store.getActions()[0]).toEqual(counterActions.increase());
  });

  it('dispatches DECREASE action', () => {
    component.find('button').at(1).simulate('click');
    expect(store.getActions()[1]).toEqual(counterActions.decrease());
  });
});

스토어에 액션이 디스패치 되면, 디스패치된 액션들의 목록을 store.getActions() 를 통하여 조회 할 수 있습니다. store.getActions() 의 반환되는 형태가 궁금하신가요? 마지막 테스트코드를 다음과 같이 수정해보세요:

  it('dispatches DECREASE action', () => {
    component.find('button').at(1).simulate('click');
    expect(store.getActions()[1]).toEqual(counterActions.decrease());
    console.log(store.getActions());
  });

확인 했다면, 방금 작성했던 console.log 코드를 지워주세요

자, 이제 카운터를 위한 모든 테스트 코드가 작성됐습니다. 이번 테스트 코드들은, 다음 흐름으로 진행되었습니다.

  1. 액션 생성 함수들이 액션을 잘 만드는가?
  2. 리듀서가 상태 변화를 제대로 일으키는가?
  3. 컴포넌트는 제대로 렌더링 되는가?
  4. 버튼이 클릭 됐을 때, 실제로 액션이 디스패치 되는가?

---> 이게 모두 다 된다면, 잘 되는 것이다!

이제, 다음으로 넘어가봅시다!

results matching ""

    No results matching ""