React Testing Library는 보통 리액트를 설치하면 기본적으로 같이 설치되어 있는 라이브러리입니다.
TDD로 프로젝트를 하며 프론트엔드 테스트 코드를 작성한 경험이 있어서 정리합니다.
정적, 동적 컴포넌트 두 가지 모두 테스팅을 할 수 있는데, 여기서 정적 테스팅이란 말 그대로 웹 페이지에 정적인 html 요소가 표시되는지 확인하는 테스팅입니다.
먼저 정적인 요소 먼저 테스팅해보도록 하겠습니다.
TDD가 늘 그렇듯 빈 Login 컴포넌트를 만들어 놓고 given-when-then 패턴으로 테스트 코드 먼저 작성합니다.
정적 테스트 코드 예시
// Login.test.tsx
import { render, screen } from "@testing-library/react";
import Login from "./Login";
describe("Login", () => {
describe("컴포넌트가 렌더링되었을 때", () => {
it("아이디 입력창이 표시된다", () => {
// when
render(<Login/>);
// then
expect(screen.getByPlaceholderText("아이디를 입력하세요")).toBeInTheDocument();
});
});
});
여기서는 getByPlaceholderText 라는 메소드로 placeholder를 통해 가져왔지만, 요소를 가져오는 방법은 여러 가지가 있으니 상황에 맞게 쓰셔야 합니다.
npm test 명령어를 통해 테스트 결과를 보면
당연하게도 실패했다는 메시지가 뜨고, 여기서부터 개발을 시작합니다.
// Login.tsx
export default function Login() {
return (
<div className="login-wrap">
<input type="text" placeholder="아이디를 입력하세요"/><br/>
</div>
);
}
요소를 만들고 나면, 모든 테스트가 성공했다는 결과가 표시됩니다.
그러나 사실 정적 테스팅은 눈으로 바로 보이는 부분이라 상황에 따라 테스트의 필요성을 느끼지 못할 수도 있지만, 동적 테스팅은 직접 실행해 봐야 성공 여부를 알 수 있으니 테스트 코드의 이점을 조금 더 받아들일 수도 있겠죠.
동적 테스트에선 fireEvent라는 메소드를 사용하여 사용자의 행동을 테스트에서 직접 시뮬레이션 할 수 있습니다.
아래 테스트 코드 예시들을 확인해 보겠습니다.
동적 테스트 코드 예시 1) 포커싱 여부 확인
describe("로그인 버튼을 눌렀을 때", () => {
describe("아이디가 입력되어 있지 않다면", () => {
it("아이디 입력창에 커서가 포커스된다", () => {
// given
render(<Login/>);
const passwordInput = screen.getByPlaceholderText("비밀번호를 입력하세요");
fireEvent.change(passwordInput, {target: {value: "password123"}});
// when
fireEvent.click(screen.getByTestId("login-button"));
// then
expect(screen.getByPlaceholderText("아이디를 입력하세요")).toHaveFocus();
});
});
});
동적 테스트 코드 예시 2) 클래스 이름 추가 여부 확인 (스타일 변경 확인 시 사용)
describe("로그인 버튼을 눌렀을 때", () => {
describe("비밀번호가 입력되어 있지 않다면", () => {
it("비밀번호 입력창에 .red 클래스가 추가된다", () => {
// given
render(<Login/>);
const idInput = screen.getByPlaceholderText("아이디를 입력하세요");
fireEvent.change(idInput, {target: {value: "id123"}});
// when
fireEvent.click(screen.getByTestId("login-button"));
// then
expect(screen.getByPlaceholderText("비밀번호를 입력하세요")).toHaveClass("red");
});
});
});
동적 테스트 코드 예시 3) useNavigate 호출 확인
const mockNavigate = jest.fn();
jest.mock("react-router-dom", () => ({
useNavigate: () => mockNavigate
}))
describe("홈 버튼을 눌렀다면", () => {
it("홈 화면으로 이동한다", () => {
// given
render(<Login/>);
// when
fireEvent.click(screen.getByTestId("home-button"));
// then
expect(mockNavigate).toBeCalled();
});
});
useNavigate를 모킹(가짜로 만드는 것)하고 모킹된 함수를 호출하는지 확인합니다.
동적 테스트 코드 예시 4) 상위 컴포넌트에 props 값 전달 확인
describe("Popup", () => {
describe("닫기 버튼을 눌렀다면", () => {
it("상위 컴포넌트에 닫기 동작을 요청한다", () => {
// given
const mockSetIsShow = jest.fn();
render(<Popup setIsShow={mockSetIsShow}/>);
// when
fireEvent.click(screen.getByTestId("close-button"));
// then
expect(mockSetIsShow).toBeCalledWith(false);
});
});
});
(심화) 동적 테스트 코드 예시 5) axios 요청 후 응답에 따른 뷰 렌더링 여부 확인
axios를 모킹하려면 아래 패키지가 필요합니다. 설치해주세요.
npm install -D axios-mock-adapter
그리고 package.json에서
"test" 명령어를 다음과 같이
"react-scripts test --transformIgnorePatterns \"node_modules/(?!@axios)/\""
로 바꿔 줍니다.
테스팅 라이브러리와 axios의 모듈 타입이 호환되지 않기 때문에 오류가 발생하기 때문입니다. (아래 참고)
"Cannot use import statement outside a module" with Axios
I have a Vue.js application where two files contain: import axios from "axios" These files are located in src/lib within the application and include the import statement on their first li...
stackoverflow.com
import MockAdapter from "axios-mock-adapter";
import axios from "axios";
describe("로그인 버튼을 눌렀을 때", () => {
describe("모든 정보가 입력되어 있다면", () => {
let mockAdapter: MockAdapter;
beforeAll(() => {
mockAdapter = new MockAdapter(axios);
});
it("서버에 로그인 요청을 보내고, 성공시 화면에 로그인 성공 메시지를 표시한다", async () => {
// given
mockAdapter.onGet("https://doringri.tistory.com").reply(200, {
data: {
id: "id123"
}
});
render(<Login/>);
const idInput = screen.getByPlaceholderText("아이디를 입력하세요");
const passwordInput = screen.getByPlaceholderText("비밀번호를 입력하세요");
fireEvent.change(idInput, {target: {value: "id123"}});
fireEvent.change(passwordInput, {target: {value: "password123"}});
// when
fireEvent.click(screen.getByTestId("login-button"));
// then
await waitFor(() => expect(screen.findByText("id123님 환영합니다.")));
});
});
});
비동기 통신인 axios를 검증하기 때문에 async 함수가 되었고, React Testing Library에서 제공하는 함수 waitFor를 사용하여 통신이 끝날 때까지 기다려줘야 합니다.
mockAdapter의 reply에는 요청시 응답받을 내용을 입력해줍니다.
mockAdapter의 요청 URL은 정확하게 입력해야 하며, 반드시 render 되기 전에 모킹해야 합니다.
여기까지 몇 가지 예시를 통해 React Testing Library로 테스트 코드를 작성하는 방법을 살펴봤습니다.
여기서 나온 예시들을 잘 응용하면 많은 경우들에 대응하는 테스트 코드들을 작성할 수 있겠죠.
테스트 코드는 요구사항을 잘 충족했는지 한눈에 확인할 수 있는 방법이지만, 코드 리팩토링 시 테스트 코드까지 함께 바꿔줘야 하는 불편함도 있습니다.
시간 자원이 한정된 상황이라면 꼭 TDD를 하지 않더라도, 핵심 기능이 잘 수행되고 있는지만이라도 테스트 코드를 작성한다면 런칭 전 버그를 많이 줄일 수 있을 것입니다.
전체 코드는 github에서 확인해주세요.
GitHub - ParkBible/React-Testing-Library: 리액트 테스트코드 예시
리액트 테스트코드 예시. Contribute to ParkBible/React-Testing-Library development by creating an account on GitHub.
github.com
'IT > React & Next.js' 카테고리의 다른 글
[Next.js] Next.js 프로젝트 배포하기 (0) | 2024.08.07 |
---|---|
[IT/Next.js] Next.js의 SSR 구현 (0) | 2024.07.28 |
[Next.js] 코드 수정사항 반영이 되지 않을 때 (feat.HMR) (0) | 2024.03.24 |
[React/TypeScript] setInterval 안에서 state가 변경되지 않을 때 (useInterval 사용법) (0) | 2023.12.16 |
[Node.js/React] 로컬 static 파일 가져오기 (0) | 2023.12.02 |