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();
});
});
컨테이너 컴포넌트를 렌더링 할 때에는,
그 다음에는, 각 버튼 클릭을 시뮬레이트 해보고, 액션이 잘 디스패치 됐는지 확인해보겠습니다.
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 코드를 지워주세요
자, 이제 카운터를 위한 모든 테스트 코드가 작성됐습니다. 이번 테스트 코드들은, 다음 흐름으로 진행되었습니다.
- 액션 생성 함수들이 액션을 잘 만드는가?
- 리듀서가 상태 변화를 제대로 일으키는가?
- 컴포넌트는 제대로 렌더링 되는가?
- 버튼이 클릭 됐을 때, 실제로 액션이 디스패치 되는가?
---> 이게 모두 다 된다면, 잘 되는 것이다!
이제, 다음으로 넘어가봅시다!