오늘은 리액트에서의 test에 대해 알아보겠다.
테스트 코드란, 소프트웨어의 기능과 동작을 테스트할 수 있는 코드이다. 개발자가 작성한 코드를 실행하고 예상한 결과가 나오는 지 확인하는 데 사용된다.
테스트 코드엔 단위 테스트(Unit Testing), 통합 테스트(Integration Testing), 시스템 테스트(System Testing) 등 다양한 종류가 있고, 종류에 따라 테스트의 대상과 범위가 다르다. 그러나 모두 기대한 입력값과 출력값을 반환하는지를 test한다.
다음은 여러가지 테스트 코드 종류이다.

몇몇 테스팅 도구들은 변경사항이 생기고 결과 값이 출력되는 과정에서 매우 빠른 피드백 루프를 제공하지만 실제 브라우저 동작을 정확히 구현하지 않는다(ex. Jest).
어떠한 도구들은 현실 브라우저 환경과 동일하게 구현되지만 반복 작업 속도가 느리고 지속적 통합 서버 환경(CI)에 취약하다(ex. Cypress).
유닛 테스트: 함수 하나하나와 같이 코드의 작은 부분을 테스트하는 것
통합 테스트: 서로 다른 시스템들의 상호작용이 잘 이루어 지는지 테스트하는 것
컴포넌트 안에서는 유닛테스트와 통합테스트의 차이는 명확하지 않다.
👉 프로젝트에 따라 적합한 테스트를 도입해야 할 것
특징
사용법
yarn add jest --dev
"scripts": {
"test": "jest"
}
describe('계산 테스트', () => {
const a = 1, b = 2;
test('a + b는 3이다.', () => {
expect(a + b).toEqual(3);
});
});
다음 코드를 작성 후 터미널에서 test를 실행하면 아래와 같이 뜬다. (예시)

테스트 실행 명령어를 입력하면 애플리케이션 내의 모든 테스트 파일이 실행된다. (특정 테스트 파일만 실행시키고 싶다면 명령어 뒤에 경로 입력해주기)
test("테스트 설명", () => {
expect("검증 대상").toXxx("기대 결과");
});
일반적인 패턴은 보통 위의 예시와 같다.
toXxx 부분에서 사용되는 함수를 Test Matcher라고 한다.
test("number 0 is falsy but string 0 is truthy", () => {
expect(0).toBeFalsy();
expect("0").toBeTruthy();
});test("array", () => {
const colors = ["Red", "Yellow", "Blue"];
expect(colors).toHaveLength(3); //배열의 길이 체크
expect(colors).toContain("Yellow"); //존재 여부 체크
expect(colors).not.toContain("Green");
});test("string", () => {
expect(getUser(1).email).toBe("user1@test.com"); //문장 통째로 일치하는지
expect(getUser(2).email).toMatch(/.*test.com$/); //정규식 기반
}); function getUser(id) {
if (id <= 0) throw new Error("Invalid ID");
return {
id,
email: `user${id}@test.com`,
};
}
test("throw when id is non negative", () => {
expect(() => getUser(-1)).toThrow();
expect(() => getUser(-1)).toThrow("Invalid ID");
});
먼저 라이브러리를 소개하기 전에, 행위 주도 테스트와 구현 주도 테스트에 대해 알아보고 들어가겠다.
<h2 class="title">제목</h2>
👉 즉, h2태그를 span태그로 수정할 시 테스트 에러 발생. 화면에 그려지는 UI는 똑같을텐데 이 에러가 유의미한지 의문이다.
반면 행위 주도 테스트는 위의 구현 주도 테스트의 단점을 보완하여, 화면이 어떻게 변화하는지에 초점을 맞추어 테스트를 작성한다. (즉, 사용자의 실제 경험 위주로 테스트를 작성)
리액트 테스팅 라이브러리가 등장하기 전, Airbnb에서 만든 Enzyme이라는 테스팅 라이브러리가 많이 사용되었다.
Enzyme
React Testing Library
RTL 설치 이전에, 리액트 프로젝트에 테스팅 프레임워크(jest, vitest 등)가 설치돼있어야 한다.
cf) create-react-app으로 생성된 프로젝트는 jest와 테스팅 라이브러리가 이미 설치되어있음
RTL 설치
yarn add -D @testing-library/react
jest-dom을 설치 (jest-dom에 특화된 custom matcher 제공)
yarn add -D @testing-library/jest-dom
RTL 사용할 때 필수로 해줘야 하는 설정
// create-react-app으로 생성된 프로젝트일 경우
import "@testing-library/react/cleanup-after-each";
import "@testing-library/jest-dom/extend-expect";
// vitest를 사용하는 프로젝트일 경우
import "@testing-library/jest-dom/vitest";
import { cleanup } from "@testing-library/react";
import { afterEach } from "vitest";
afterEach(cleanup);
주요 API
import { render, screen fireEvent } from "@testing-library/react";
render(<YourComponent />);
const button = screen.getByText(/click me/i);
fireEvent.click(button);
아래 코드는 이메일과 비밀번호 입력란, 버튼으로 구성된 간단한 로그인폼 컴포넌트이다. 내부 상태에 따라 UI 변화가 생길 수 있는 컴포넌트에 해당한다.
import React from "react";
function LoginForm({ onSubmit }) {
const [email, setEmail] = React.useState("");
const [password, setPassword] = React.useState("");
return (
<>
<h2>Login</h2>
<form onSubmit={() => onSubmit()}>
<label>
이메일
<input
type="email"
placeholder="user@test.com"
value={email}
onChange={({ target: { value } }) => setEmail(value)}
/>
</label>
<label>
비밀번호
<input
type="password"
value={password}
onChange={({ target: { value } }) => setPassword(value)}
/>
</label>
<button disabled={!email || !password}>로그인</button>
</form>
</>
);
}
아래는 비활성화 되어 있던 로그인 버튼이 이메일, 비밀번호 입력 후에 활성화되는지에 대한 테스트 코드이다.
import React from "react";
import { render, fireEvent } from "@testing-library/react";
import LoginForm from "./LoginForm";
describe("<LoginForm />", () => {
it("enables button when both email and password are entered", () => {
render(<LoginForm onSubmit={() => null} />);
const button = screen.getByRole("button", {
name: /로그인/i,
});
expect(button).toBeDisabled();
const email = screen.getByRole("textbox", {
name: /이메일/i,
});
const password = screen.getByLabelText(/비밀번호/i);
fireEvent.change(email, { target: { value: "user@test.com" } });
fireEvent.change(password, { target: { value: "Test1234" } });
expect(button).toBeEnabled();
});
});
우와 테스트 코드에 대한 이해부터 Jest랑 RTL의 사용 예시까지 하나하나 다뤄주셨네요!! 덕분에 흐름에 따라 이해하기 쉬웠던 것 같아요! TDD를 도입해보고 싶었는데 작성해주신 고려할 부분들을 생각해보고 알맞게 도입해야겠습니다.