React Testing Library로 단위 테스트, 통합 테스트 작성하기

zz1·2024년 4월 24일
post-thumbnail

예전에 했던 캘린더 프로젝트에서 개선하고 싶은 부분이 생겼다.

기존에 등록해두었던 일정을 수정하면, 일정의 순서가 바뀌는 현상이 발생했다. array에서 해당 아이템을 삭제하고 새로운 아이템을 concat하는 방법을 썼기 때문!
처음에는 테스트 실패가 뜨지만 코드를 작성한 후 테스트 성공이 뜨는 과정을 겪어보고 싶었기에.. 🎶 이걸 고치는 김에 테스트 코드를 작성해보기로 했다.

유닛 테스트

import { render, screen, fireEvent } from '@testing-library/react';
import { ColorThemeProvider } from '../../Context/ColorThemeContext';
import TodoAddModal from '../TodoAddModal';

describe('<TodoAddModal>', () => {
  it('새로운 일정을 등록하면 Props로 전달된 함수가 호출되고, 폼이 리셋됩니다.', () => {
    const closeModal = jest.fn();
    const addSchedule = jest.fn();
    render(
      <TodoAddModal
        date={new Date()}
        open={true}
        closeModal={closeModal}
        schedule={{}}
        addSchedule={addSchedule}
      />,
      {
        wrapper: ColorThemeProvider,
      }
    );

    const titleInput = screen.getByPlaceholderText(/title/i);
    const descriptionInput = screen.getByPlaceholderText(/description/i);
    const submitBtn = screen.getByRole('button', {
      name: /SUBMIT/i,
    });

    fireEvent.change(titleInput, { target: { value: 'todo' } });
    fireEvent.change(descriptionInput, {
      target: { value: 'todo description' },
    });
    fireEvent.click(submitBtn);

    expect(closeModal).toHaveBeenCalledTimes(1);
    expect(addSchedule).toHaveBeenCalledTimes(1);

    expect(titleInput.value).toBe('');
    expect(descriptionInput.value).toBe('');
  });
});

관련된 컴포넌트의 유닛 테스트를 작성해봤다.
캘린터 프로젝트는 Context를 사용하고 있기 때문에 wrapper 옵션으로 Provider를 넣어줬다.
참고한 블로그: https://twinstae.github.io/component-testing-a11y-context/

또한 내 프로젝트는 CSS Module을 사용하고 있어서 처음에는 오류가 떴는데, identity-obj-proxy를 설치하니 해결되었다. 🍀

<TodoAddModal /> 은 투두 아이템을 입력받는 폼이기 때문에
1. 제출 되었을 때 Props로 전달받은 함수가 잘 실행되는지
2. 제출되고 난 후에 폼이 잘 초기화 되는지
확인하려는 목적이었다.

아쉬운 점
지금 생각해보니 함수 호출 횟수 뿐만 아니라 인자가 잘 전달되었는지도 확인했어야 되는 거 아닌가 싶다.
그리고 중복 코드가 길어질 것 같아 합쳤는데 1번, 2번을 쪼개서 작성해야 됐나? 싶기도 하다.

통합 테스트

사실 통합 테스트를 할 생각은 없었는데,
일정 등록 > 일정 수정 > 순서 확인은 유닛 테스트로 될 것 같지가 않았다. 통합 테스트는 찾아도 잘 안 나와서 긴가민가한 상태로 코드를 작성했다...
https://coderpad.io/blog/development/how-to-write-integration-tests-with-jest-and-react-testing-library/
⬆ 해당 글을 보며 통합 테스트를 어떻게 진행하는지 파악했다.

import { render, screen, fireEvent } from '@testing-library/react';
import MyCalendarPage from '../MyCalendarPage';
import { ColorThemeProvider } from '../../Context/ColorThemeContext';

describe('<MyCalendarPage>', () => {
  it('일정을 수정했을 경우에도 일정 array 순서가 유지되어야 한다.', () => {
    render(<MyCalendarPage />, {
      wrapper: ColorThemeProvider,
    });

    //1. 일정 등록
    const addBtn = screen.getByRole('button', {
      name: /addBtn/i,
    });
    const titleInput = screen.getByPlaceholderText(/title/i);
    const submitBtn = screen.getByRole('button', {
      name: /SUBMIT/i,
    });

    const todoList = ['todo1', 'todo2'];
    for (let todo of todoList) {
      fireEvent.click(addBtn);
      fireEvent.change(titleInput, { target: { value: todo } });
      fireEvent.click(submitBtn);
    }

    let todoItems = screen.queryAllByTestId('todoItem');
    expect(todoItems.length).toBe(2);

    //2. 일정 수정
    const modifyBtn = screen.queryAllByTestId('modify')[0];
    fireEvent.click(modifyBtn);

    const modifyTitleInput = screen.getByPlaceholderText(/title edit/i);
    expect(modifyTitleInput.value).toBe(todoList[0]);

    const editSubmitBtn = screen.getByRole('button', {
      name: /OK/i,
    });
    fireEvent.click(editSubmitBtn);

    const closeDetail = screen.getByTestId('closeDetail');
    fireEvent.click(closeDetail);

    const newTodoItems = screen.queryAllByTestId('todoItem');
    expect(newTodoItems.length).toBe(2);

    //3. 최종 순서 확인
    const items = screen.getAllByText(/todo/i);
    expect(items[0].innerHTML).toBe(todoList[0]);
  });
});

이게 맞는 건지 코드를 작성하면서도 긴가민가 했다.

처음에는 (당연하게도) 테스트가 실패했다.
테스트 코드를 다 작성한 후 실제 코드를 고쳤다.

코드를 고친 후 테스트를 다시 실행해봤다!
무사히 통과했다 😄

아쉬운 점

const titleInput = screen.getByPlaceholderText(/title/i);
    const submitBtn = screen.getByRole('button', {
      name: /SUBMIT/i,
    });

const editSubmitBtn = screen.getByRole('button', {
      name: /OK/i,
    });
  1. 원래는 두 버튼 다 OK라는 글자를 가지고 있었는데, 글자가 같다 보니getByRole로 가져오는 과정에서 오류가 발생하는 것 같았다. 그래서 submitBtn의 글자를 SUBMIT으로 바꿨는데, 이 과정에서 유닛 테스트의 코드를 바꾸지 않아 오류가 났다! 화면에 보이는 글자와 상관 없이 가져올 수 있게끔 코드를 작성하면 좋았을 텐데, 아쉬웠다.
  2. 프로젝트를 개발할 때 태그가 계속 중첩되는 게 싫어서 icon에 바로 onClick 이벤트를 연결해줬는데 이게 좀 후회됐다. 버튼으로 감쌌다면 테스트 코드를 작성할 때 좀 더 편했을 것 같다.
  3. 테스트 코드의 3. 최종 순서 확인에서 화면에 있는 TodoItem들의 순서를 비교했어야 했는데, 원래 작성하려던 코드가 자꾸 오류가 나서 화면에 있는 todo 텍스트를 가져와 비교를 했다. (todo1, todo2라는 이름으로 일정을 등록했기 때문에 todo 텍스트를 가져왔다.) 나쁜 코드 같아서 고치고 싶다.

느낀 점
테스트 코드 작성하는 일이 간단해 보였는데 생각보다 힘들었다.
무엇을 테스트해야 되는지 결정하는 일이 제일 어려웠다.

0개의 댓글