필터 기능 구현 중에 다중 선택, 조건부 선택 등 로직이 복잡하여, 이 기능이 제대로 동작하는지 확인하는 작업이 필요하다고 생각했다. 그래서 테스트 전략에 관하여 공부도 할겸 테스트 코드를 도입하기로 결정했다.
현재 진행중인 프로젝트는 create-react-app으로 프로젝트를 구성했기 때문에 기본적으로 jest와 react testing library가 설치되어 있다. npm test 명령어로 테스트 코드를 실행할 수 있다.
Jest는 Facebook에서 개발한 JavaScript 테스팅 프레임워크로, 큰 설정 없이 바로 사용할 수 있는 것이 가장 큰 장점이다. React 애플리케이션뿐만 아니라, 모든 JavaScript 프로젝트에 사용할 수 있다. Jest는 빠른 성능, 스냅샷 테스팅, 목(mock) 함수 등 다양한 기능을 제공하여 테스트 과정을 간소화한다.
React Testing Library는 리액트 컴포넌트를 테스트하기 위해 만들어진 라이브러리다. 이 라이브러리의 핵심 철학은 "더 적은 코드로 더 많은 것을 테스트"하는 것이다. React Testing Library는 DOM에서 컴포넌트를 렌더링하고, 사용자가 실제로 앱을 사용하는 방식대로 테스트를 진행한다. 즉, 구현 세부 사항보다는 앱의 기능적인 측면에 초점을 맞춘다.
Jest와 React Testing Library를 함께 사용하면, 리액트 컴포넌트의 단위 테스트와 통합 테스트를 매우 효율적으로 진행할 수 있다. Jest가 테스트 환경을 제공하고, React Testing Library는 사용자 중심의 테스트 작성을 도와준다.
(아래는 vitest를 이용하여 작성한 테스트 코드입니다. vitest를 사용한 이유는 다음 포스팅에서...)
import { fireEvent, render, screen } from '@testing-library/react';
import RoommateFilterPage from './RoommateFilterPage';
import { expect, test, describe } from "vitest";
import '@testing-library/jest-dom';
describe('YourComponent', () => {
test('사용자가 인원을 선택할 때 상태가 올바르게 업데이트되어야 한다', () => {
render(<RoommateFilterPage />);
// '2인'과 '3인' 선택
fireEvent.click(screen.getByTestId('2인'));
fireEvent.click(screen.getByTestId('3인'));
// '2인'과 '3인'이 선택되었는지 확인
expect(screen.getByTestId('2인')).toBeChecked();
expect(screen.getByTestId('3인')).toBeChecked();
// '2인' 선택 해제
fireEvent.click(screen.getByTestId('2인'));
// '2인'이 선택 해제되었는지 확인
expect(screen.getByTestId('2인')).not.toBeChecked();
// '3인'은 여전히 선택되어 있는지 확인
expect(screen.getByTestId('3인')).toBeChecked();
});
test('남성을 선택하면 A동과 E동만 선택 가능해야 한다', () => {
render(<RoommateFilterPage />);
// 남성 선택
fireEvent.click(screen.getByLabelText('남성'));
// A동과 E동이 선택 가능한지 확인
expect(screen.getByLabelText('A동')).not.toBeDisabled();
expect(screen.getByLabelText('E동')).not.toBeDisabled();
// B동, C동, D동이 선택 불가능한지 확인
expect(screen.getByLabelText('B동')).toBeDisabled();
expect(screen.getByLabelText('C동')).toBeDisabled();
expect(screen.getByLabelText('D동')).toBeDisabled();
});
test('남성을 선택하고 E동을 선택하면 호실 유형 선택이 가능해야 한다', () => {
render(<RoommateFilterPage />);
// 남성 선택
fireEvent.click(screen.getByLabelText('남성'));
// E동 선택
fireEvent.click(screen.getByLabelText('E동'));
// 호실 유형 체크박스가 렌더링되었는지 확인
expect(screen.getByLabelText('2인실')).toBeInTheDocument();
expect(screen.getByLabelText('4인실')).toBeInTheDocument();
// 호실 유형 체크박스가 선택 가능한지 확인
expect(screen.getByLabelText('2인실')).not.toBeDisabled();
expect(screen.getByLabelText('4인실')).not.toBeDisabled();
});
test('남성을 선택하고 A, E동을 선택했다가 여자를 선택하면 A,E 동의 선택이 해제된다', () => {
render(<RoommateFilterPage />);
// 남성 선택
fireEvent.click(screen.getByLabelText('남성'));
// A, E동 선택
fireEvent.click(screen.getByLabelText('E동'));
fireEvent.click(screen.getByLabelText('A동'));
// 기숙사 동 체크박스가 체크되었는지 확인
expect(screen.getByLabelText('E동')).toBeChecked();
expect(screen.getByLabelText('A동')).toBeChecked();
expect(screen.getByLabelText('B동')).toBeDisabled();
expect(screen.getByLabelText('C동')).toBeDisabled();
expect(screen.getByLabelText('D동')).toBeDisabled();
//여성 선택
fireEvent.click(screen.getByLabelText('여성'));
// 기숙사 동 체크박스가 선택 가능한지 확인
expect(screen.getByLabelText('E동')).not.toBeChecked();
expect(screen.getByLabelText('A동')).not.toBeChecked();
expect(screen.getByLabelText('E동')).toBeDisabled();
expect(screen.getByLabelText('A동')).toBeDisabled();
expect(screen.getByLabelText('B동')).not.toBeDisabled();
expect(screen.getByLabelText('C동')).not.toBeDisabled();
expect(screen.getByLabelText('D동')).not.toBeDisabled();
});
});
decribe를 통해 여러 테스트 케이스를 묶고, test(it)안에 개별 테스트 케이스를 작성한다.
@testing-library/react에서 불러온 render 함수를 이용하여 테스트할 컴포넌트를 렌더링한 후, screen 객체를 통해 렌더링된 요소들에 접근할 수 있다. fireEvent 객체는 React Testing Library에서 제공하는 유틸리티 함수 중 하나로 테스트 중에 이벤트를 발생시키는 데 사용한다.
expect 함수 안에는 테스트 할 값을 넣고, 이를 matcher(toBeDisabled 등)를 통해 일치하는지의 여부를 확인한다.
테스트 코드를 잘 작성하는 것도 중요하겠지만, 우선적으로 무엇을 테스트할지를 정하는 것이 더욱 중요하다는 생각이 들었다. 모든 UI 전환이나 작은 기능에 대해서 전부 케이스를 작성하여 테스트 코드를 작성하는 것은 매우 비효율적일 것이다. 이 프로젝트는 TDD기반으로 개발하는 것이 아니기 때문에 기준을 잘 설정하여 테스트 코드를 작성해나가며 더욱 완전한 기능 개발을 이어나갈 예정이다.
+

msw를 이용하여 api 모킹 테스트를 진행했을 때 여러가지 문제점에 직면하였다. 이는 다음 포스팅에서 풀어볼 예정이다.