Skip to content

테스트 코드 작성법📚

shinhyogeun edited this page Dec 3, 2021 · 2 revisions

우리는 Jest로 한다.

  • 최대한 Describe - Context - it으로 작성해보는 것이 좋다. (안되면 test함수도 좋아요! 👍 )
  • test는 비동기로 실행된다는 것! 위에서부터 아래로의 보장이 없다.
  • 함수를 테스트함을 원칙으로!
  • npx jest —watchAll로 항상 켜두기!

test는 최대한 남탓으로!

하나의 함수(A함수) 안에서 바로 다른 함수(B함수)를 쓴다면 A함수를 테스트 할 수 없는 것은 아니다. 다만 A함수에서 무엇이 잘못되면 그것이 A가 잘못 된 것인지 B가 잘못된 것인지 알 수 없다. 예를 들어보자

function B(a,b){
	return a+b
}

function A(a,b){
	const c = a//2   
	const d = b//2
	return c + d + B(a,b)
}

위의 A함수를 테스트한다고 해보자. A함수는 하는 일이 2가지 이다. 인자를 이용해 상수들을 만드는 것과 B함수를 실행하는 것 이렇게 2가지이다. 이때 우리는 A함수의 하는 책임을 줄일 필요가 있다. return부분에서 B의 함수가 이상해져서 A의 결과가 이상하다면 그건 B의 잘못이지 A의 잘못이 아니다. 이럴 때는 우리가 B(a,b)는 그냥 3이라고 가정하고 테스트를 돌려줘 혹은 그냥 3을 반환하는 함수로 B를 바꿔치기하고 테스트를 돌려줘 같은 식의 명령을 내릴 수가 있다. 이렇게 A함수의 책임을 최대한 줄여주고 다른 함수들의 역할을 없애버리는 것이 중요하다. 이러지 않으면 나중에 테스트가 깨져도 깨진 테스트에서 뭐때문에 테스트가 깨졌는지 알 수 없기 때문이다.

test를 위한 Mocking

그렇다면 우리는 B함수의 역할을 바꿔치기 해야한다. 즉 B함수를 가짜 함수로 바꿔야 하는데 이것을 Mocking한다라고 한다.

그러면 Redux를 Mocking해보자! 컴포넌트에서 리덕스를 사용한다면 외부의 함수를 사용하는 것이기에 Mocking하지 않고는 test할 수 없다. 그래서 Redux를 가짜로 만들어줘야한다. 이건 너무 어렵기에 test를 위한 Redux-mocking 라이브러리가 있다.(redux-mock-store) 이것을 사용해서 해보자.

전반적인 흐름을 살펴보자 컴포넌트에서는 useSelector를 이용해서 원하는 부분을 가져온다.

그리고 useDispatch를 실행해 dispatch를 얻고 dispatch를 이용해서 redux의 상태를 업데이트 한다.

밑의 컴포넌트는 제 프로젝트의 일부입니다.

import React, { useCallback } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import Playlist from '../components/Playlist';
import { listenMusic, deletePlaylistMusic } from '../redux/slice';
import { get } from '../services/utils';

export default function PlaylistContainer() {
  const dispatch = useDispatch();

  const playlist = useSelector((obj) => obj['playlist']);
  const player = useSelector((obj) => obj['player']);

  const handleClickListen = useCallback((paused, music) => {
    const resultToken = 0;
    dispatch(listenMusic(paused, { resultToken, ...music }));
  }, [dispatch, listenMusic]);

  const handleClickDelete = useCallback((music) => {
    dispatch(deletePlaylistMusic(music));
  }, [dispatch, deletePlaylistMusic]);

  return (
    <Playlist
      playlist={playlist}
      player={player}
      onClickListen={handleClickListen}
      onClickDelete={handleClickDelete}
    />
  );
}

Playlist라는 컴포넌트는 하단 컴포넌트입니다. 여기서 중요하게 보면 되는 부분은 PlaylistContainer의 역할입니다. PlaylistContainer는 리덕스와 소통을 하는 역할을 하는 컴포넌트입니다. 즉 리덕스 소통 방법을 정의하고 하단 컴포넌트에 내려줍니다. 이 컴포넌트를 테스트하려면 실제 리덕스의 저장소를 건드릴 수 있기에 테스트에서 이를 잘 목킹해줘야 한다.

../__mocks__/react-redux

export const useDispatch = jest.fn();
export const useSelector = jest.fn();
import React from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { fireEvent, render } from '@testing-library/react';
import PlaylistContainer from '../PlaylistContainer';
import musics from '../../../fixtures/musics';
import { filterMusicInfo } from '../../services/utils';
import music from '../../../fixtures/music';

jest.mock('react-redux');
describe('PlaylistContainer', () => {
  const dispatch = jest.fn();

  useDispatch.mockImplementation(() => dispatch);
  useSelector.mockImplementation((selector) => selector({
    playlist: musics.items.map((item) => filterMusicInfo(item)),
    player: { resultToken: 0, ...music },
  }));

  beforeEach(() => jest.clearAllMocks());

  it('듣기 버튼을 누르면 dispatch가 실행된다.', () => {
    const { container } = render(<PlaylistContainer />);

    fireEvent.click(container.querySelector('li'));

    expect(dispatch).toBeCalled();
  });
});

테스트 코드의 실행 순서를 따라서 보자.

  • 실행 순서
    1. 각종 임포트를 해온다.
    2. react-redux를 목킹한다. (이후 밑에서
const { container } = render(<PlaylistContainer />);

이 부분에서 컴포넌트를 만들 때 이제는 내가 redux-react를 목킹했기 때문에 내가 만든 가짜 함수를 쓰게 된다. 3. useDispatch와 useSelector를 mockImplementation를 이용해서 가짜로 만든다. 4. 테스트를 실행한다.

각종 유용한 Matcher 함수들

참조하시면 좋아요~📚

매처(matcher) 사용하기 · Jest

Clone this wiki locally