React 컴포넌트 테스트 코드 작성하기

React 컴포넌트 테스트 코드 작성하기

프로젝트
프로젝트
카테고리
Dev
작성일
2023-12-10
태그
React
Dev
작성자
꾸생
상태
공개
이번 주말은 이전 만들어 뒀던 리액트 보일러플레이트 마이그레이션과 Jest 테스트 코드 학습겸 <Counter /> 컴포넌트에 테스트 코드를 작성하는데 시간을 다 보냈다. 테스트 코드는 다른 사람이 작성한 코드에 컨벤션을 익혀둘 필요가 있어보이고 테스트 파일을 어떤 식으로 관리하는 지와 무엇을 중점으로 테스트해야 하는지 등 생각할게 많아 보인다.

📦 초기 설정

Install: Jest

yarn add -D jest @types/jest babel-jest ts-jest ts-node jest-environment-jsdom

Install: React Testing Library

yarn add -D @testing-library/react @testing-library/dom @testing-library/jest-dom @testing-library/user-event

jest.config.ts

/** @type {import('ts-jest').JestConfigWithTsJest} */ export default { preset: 'ts-jest', testPathIgnorePatterns: ['<rootDir>/node_modules/'], moduleDirectories: ['node_modules', '<rootDir>/'], // roots: ['<rootDir>/src/__tests__/'], transform: { '^.+\\.(ts|tsx)$': ['ts-jest'] }, moduleNameMapper: { //'\\.(css|less|svg)$': 'identity-obj-proxy', '@/(.*)$': '<rootDir>/src/$1', // @ <= alias path }, testEnvironment: 'jsdom', setupFilesAfterEnv: ['<rootDir>/jest.setup.ts'], }

babel.config.js

module.exports = { presets: ['@babel/preset-env', ['@babel/preset-react', { runtime: 'automatic' }]], }

jest.setup.ts

import '@testing-library/jest-dom'

⚒️ 주로 사용하는 함수

📌 describe(desc, func)

describe 함수는 테스트를 그룹화하고 설정을 제공하는데 사용. 테스트 모음 내에 여러 개의 test를 포함할 수 있음.

📌 test(content, func)

테스트 케이스를 정의하는 함수. 단일 테스트 케이스를 정의하고 실행. 각 테스트 케이스는 독립적으로 실행되고, expect 함수를 사용해 예상 결과를 검증할 수 있다.
describe('sum module', () => { test('adds 1 + 2 to equal 3', () => { expect(sum(1, 2)).toBe(3); }); });

📌 expect(), toBe()

테스트 케이스를 정의하는 함수. 단일 테스트 케이스를 정의하고 실행한다. 각 테스트 케이스는 독립적으로 실행되고, expect 함수를 사용해 예상 결과를 검증할 수 있다.
describe('sum module', () => { test('adds 1 + 2 to equal 3', () => { expect(sum(1, 2)).toBe(3); }); });
expect에 결과 값과 toBe 함수에 결과 값으로 테스트 성공 여부를 체크.

📌 toBeInTheDocument()

import { screen, render } from "@testing-library/react"; import App from './App' describe("앱을 렌더링합니다.", () => { test("버튼이 있습니다.", () => { const render(<App />); const buttonEl = screen.getByTestId( 'app-button') expect(buttonEl).toBeInTheDocument() }) })
toBeInTheDocument() 함수는 문서에 버튼이 존재하는지 검사한다. 렌더링 여부 테스트라고 해도 무방

render() 테스트를 위한 컴포넌트 렌더링 함수

getBy~

~로 시작하는 쿼리는 조건에 일치하는 DOM 엘리먼트 하나를 선택. 만약에 없다면 에러 발생. 현재까지는 getBy~ 함수로도 테스트를 진행하는데 무리가 없었다. query~ 도 있고 find~ 도 있다.
ex) getByText, getByLabelText, getByAltText, getByPlaceholderText

🎆 렌더링 테스트

// counter.test.tsx import { render, screen } from '@testing-library/react' import Counter from '.' describe('<Counter /> Test', () => { const renderCounter = () => render(<Counter />) test('<CountText/> render test', () => { renderCounter() const countEle = screen.getByTestId('counter-text') expect(countEle).toBeInTheDocument() }) test('<Counter/> Buttons render test', () => { renderCounter() const upButton = screen.getByTestId('counter-up-button') const downButton = screen.getByTestId('counter-down-button') const clearButton = screen.getByTestId('counter-clear-button') expect(upButton && downButton && clearButton).toBeInTheDocument() }) }
컴포넌트 각각의 Dom Element 요소 속성에 data-testid 값을 할당했다. querySelector()를 사용하는 것보다 용도에 맞는 query 함수가 제공되므로 알맞게 사용한다. querySelector()DOM 요소를 찾는다면 if문으로 값을 체크까지 해줘야하므로 알맞은 query 함수를 사용하자(해당 DOM 요소룰 못찾으면 에러로 종료시켜 버리기 때문에 간편함 👍)
<button type="button" data-testid="counter-up-button">Up<button>

🌀 Jest 라이프사이클

컴포넌트가 복잡해지면 자연스레 테스트 코드도 복잡해지는데, 자연스레 사용할 것 같다. 강의에서 DB서버에 연결하는데 사용 했던 것 같다.
  • beforeAll : 테스트 파일 내 모든 테스트 작업 전 한번만 실행
  • beforeEach : 모든 테스트 작업 전에 실행
  • afterAll : 테스트 파일 내 모든 테스트 작업 종료 후 한번만 실행
  • beforeAll : 모든 테스트 종료 후 실행

🗑️ 빌드 시 data-testId 속성 제거하기

data-testId 속성을 사용해 테스트 대상 엘리먼트를 찾는데, 빌드 단계에서 모두 제거하고 싶어 관련 내용을 찾아 봤고 Vite를 지원하는 라이브러리가 있었다.
yarn add -D react-remove-attr
... import removeAttr from 'react-remove-attr' ... export default defineConfig(({ mode }) => { const removeAttrPlugin = isProd && removeAttr({ extensions: ['jsx', 'tsx'], attributes: ['data-testid'] }) return { plugins: [removeAttrPlugin(), ...] } }) ...
다만 해당 라이브러리는 GPL-3.0 라이센스를 가지고 있다.