들어가며
벨로퍼트님의 테스팅 튜토리얼을 공부한 내용을 정리하는 글입니다.
비동기적으로 바뀌는 컴포넌트 UI 테스트
const { useState, useEffect } = require("react");
const DelayedToggle = () => {
const [toggle, setToggle] = useState(false);
useEffect(() => {
setInterval(() => {
setToggle((prev) => !prev);
}, 1000);
}, []);
return (
<div>
상태 : <span>{toggle ? "On" : "Off"}</span>
{toggle && <div>토글이 켜졌다!</div>}
</div>
);
};
export default DelayedToggle;
다음과 같이 1초마다 상태 값이 바뀌는 컴포넌트가 있습니다.
비동기가 포함된 컴포넌트는 어떤 방식으로 테스트를 해야할까요?
Async Utilities
react-testing-library는 Async Utilities 함수들을 제공해서 비동기 테스트를 할 수 있습니다.
Async Utilities는 2가지 함수가 있다.
waitFor
waitFor 함수를 사용하면 특정 콜백에서 에러를 발생하지 않을 때 까지 대기할 수 있습니다.
test("reveals text when toggle is On", async () => {
render(<DelayedToggle />);
const checkToggleText = () => {
return screen.getByText("토글이 켜졌다!");
};
await waitFor(checkToggleText, { timeout: 2000 });
});
waitFor 함수는 콜백 안의 함수가 에러가 발생하지 않을 때 까지 기다리다가, 대기 시간이 초과되면
테스트 케이스가 실패한다.
timeout의 기본값은 4500ms이며, 이는 변경할 수 있다.
waitForElementToBeRemoved
waitForElementToBeRemoved 함수는 특정 엘리먼트가 화면에서 사라질때까지 기다리는
함수이다.
test("removes text when toggle is OFF", async () => {
render(<DelayedToggle />);
const toggleButton = screen.getByText("토글");
fireEvent.click(toggleButton);
screen.getByText("토글이 켜졌다!");
await waitForElementToBeRemoved(
() => screen.queryByText("토글이 켜졌다!"),
{ timeout: 2000 }
);
});
테스트 방식은 waitFor과 유사하다.
REST API 호출 시 테스트
다음으로 REST API를 사용하는 경우 어떤 방식으로 테스트를 해야할까?
테스트를 위해서 먼저 라이브러리를 설치한다.
yarn add axios
JSONPlaceholder에서 제공하는 가짜 API를 사용할 계획이다.
import axios from "axios";
import { useEffect, useState } from "react";
const RestAPI = ({ id }) => {
const [userData, setUserData] = useState(null);
const [loading, setLoading] = useState(false);
const getUser = async (id) => {
setLoading(true);
try {
const response = await axios.get(
`https://jsonplaceholder.typicode.com/users/${id}`
);
setUserData(response.data);
} catch (error) {
console.log(error);
}
setLoading(false);
};
useEffect(() => {
getUser(id);
}, [id]);
if (loading || !userData) return <div>Loading...</div>;
const { username, email } = userData;
return (
<div>
<p>UserName : {username}</p>
<p>email : {email}</p>
</div>
);
};
export default RestAPI;
id 값을 props로 받아와서 API를 호출하고 그 결과 username과 email을 보여주는 컴포넌트이다.
REST API를 호출해야 하는 컴포넌트의 경우, 테스트 코드에서도 똑같이 요청을 보낼 수 있지만,
일반적으로 서버에서 API를 직접 호출하지 않고 mocking한다.
그 이유는 아직 서버의 API가 만들어지지 않은 경우, 무작정 기다리기보단 mocking 서버에서
API 명세에 따라 데이터를 넘겨주는 방식으로 빠르게 개발이 가능하기 때문이다.
mocking으로 테스트하는 방식은 다양하지만 msw 를 사용해서 진행하려고 한다.
그 이유는 axios-mock-adapter 등 보다 msw를 사용하면 실제 사용자처럼 테스트가 가능하기
때문이다.
실제 사용자처럼 테스트할 수 있다는 것은 axios-mock-adapter 같은 경우
mock.onGet('https://jsonplaceholder.typicode.com/users/1').reply(200, {
id: 1,
name: 'Leanne Graham',
username: 'Bret',
email: 'Sincere@april.biz',
address: {
street: 'Kulas Light',
suite: 'Apt. 556',
city: 'Gwenborough',
zipcode: '92998-3874',
geo: {
lat: '-37.3159',
lng: '81.1496'
}
},
phone: '1-770-736-8031 x56442',
website: 'hildegard.org',
company: {
name: 'Romaguera-Crona',
catchPhrase: 'Multi-layered client-server neural-net',
bs: 'harness real-time e-markets'
}
});
다음과 같은 방식으로 mocking 한다.
하지만 msw 를 사용하면 query, params, body 등에 따라 넘겨주는 방식을 선정할 수 있기 때문에
실제로 사용한다고 했을 때, 서버 API가 작업이 완료되어 연동하는 경우에 코드를 변경할 필요가
없다는 장점이 있기 때문이다.
msw의 자세한 내용은 앞서 작성해둔 글이 있으므로 빠르게 넘기겠다.
import { rest } from "msw";
const data = [
{
id: "1",
name: "Leanne Grahqweqweqweam",
username: "Bret",
email: "Sincere@april.biz",
address: {
street: "Kulas Light",
suite: "Apt. 556",
city: "Gwenborough",
zipcode: "92998-3874",
geo: {
lat: "-37.3159",
lng: "81.1496",
},
},
phone: "1-770-736-8031 x56442",
website: "hildegard.org",
company: {
name: "Romaguera-Crona",
catchPhrase: "Multi-layered client-server neural-net",
bs: "harness real-time e-markets",
},
},
// ...
];
const handlers = [
rest.get(
`https://jsonplaceholder.typicode.com/users/:id`,
(req, res, ctx) => {
const { id: userId } = req.params;
const result = data.filter(({ id }) => userId === id);
return res(ctx.status(201), ctx.json(result[0]));
}
),
];
export default handlers;
다음과 같이 handler를 사용할 계획이다.
import { render, screen, waitFor } from "@testing-library/react";
import { setupServer } from "msw/node";
import { rest } from "msw";
import RestAPI from ".";
const data = [
{
id: "1",
name: "Leanne Graham",
username: "Bret",
email: "Sincere@april.biz",
address: {
street: "Kulas Light",
suite: "Apt. 556",
city: "Gwenborough",
zipcode: "92998-3874",
geo: {
lat: "-37.3159",
lng: "81.1496",
},
},
phone: "1-770-736-8031 x56442",
website: "hildegard.org",
company: {
name: "Romaguera-Crona",
catchPhrase: "Multi-layered client-server neural-net",
bs: "harness real-time e-markets",
},
},
{
id: "2",
name: "Leanne Graham",
username: "Bret",
email: "Sincere@april.biz",
address: {
street: "Kulas Light",
suite: "Apt. 556",
city: "Gwenborough",
zipcode: "92998-3874",
geo: {
lat: "-37.3159",
lng: "81.1496",
},
},
phone: "1-770-736-8031 x56442",
website: "hildegard.org",
company: {
name: "Romaguera-Crona",
catchPhrase: "Multi-layered client-server neural-net",
bs: "harness real-time e-markets",
},
},
];
const server = setupServer(
rest.get(
"https://jsonplaceholder.typicode.com/users/:id",
(req, res, ctx) => {
const { id: userId } = req.params;
const result = data.filter(({ id }) => userId === id);
return res(ctx.status(201), ctx.json(result[0]));
}
)
);
describe("REST API", () => {
beforeAll(() => server.listen());
afterEach(() => server.resetHandlers());
afterAll(() => server.close());
test("calls getUser API loads userData", async () => {
render(<RestAPI id={1} />);
await waitFor(function () {
return screen.getByText("Loading...");
});
await waitFor(function () {
return screen.getByText("Bret");
});
});
});
msw 를 react-testing-library와 사용하기 위해서는 setupServer를 사용해야한다.
그리고 beforeAll을 통해서 모든 테스트 전에 서버를 실행 시켜주었다.
afterEach를 통해 종료되기 전 mocking 서버를 초기화 시키고 afterAll를 사용해 모든 테스트 후
mocking 서버를 종료하였다.
'React > 실험실' 카테고리의 다른 글
[React] styled-reset (0) | 2023.01.15 |
---|---|
[React] 나만의 알고리즘 문제 저장소 만들기 - 시작 (0) | 2023.01.13 |
[React] 벨로퍼트와 함께하는 React Testing - Todo List (0) | 2023.01.07 |
[React] 벨로퍼트와 함께하는 React Testing - react-testing-library (0) | 2023.01.06 |
[React] 벨로퍼트와 함께하는 React Testing - TDD (1) | 2023.01.05 |