๐ Stop mocking fetch (opens in a new tab)
๐๏ธ ๋ฒ์ญ ๋ ์ง: 2024.05.26
๐ง ๋ฒ์ญํ ํฌ๋ฃจ: ๋ฌ๊ธฐ(๋ฐ์ ์ฐ)
โfetchโ ๋ชจํน์ ๊ทธ๋ง!
// __tests__/checkout.js
import * as React from "react";
import { render, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { client } from "~/utils/api-client";
jest.mock("~/utils/api-client");
test('clicking "confirm" submits payment', async () => {
const shoppingCart = buildShoppingCart();
render(<Checkout shoppingCart={shoppingCart} />);
client.mockResolvedValueOnce(() => ({ success: true }));
userEvent.click(screen.getByRole("button", { name: /confirm/i }));
expect(client).toHaveBeenCalledWith("checkout", { data: shoppingCart });
expect(client).toHaveBeenCalledTimes(1);
expect(await screen.findByText(/success/i)).toBeInTheDocument();
});
์ด ํ ์คํธ๋ ๋น์ ์ด **โCheckoutโ**๊ณผ โ/checkoutโ ์๋ํฌ์ธํธ์ ์ค์ API์ ์๊ตฌ ์ฌํญ์ ์์ง ๋ชปํ๋ค๋ฉด ์ ๋ง๋ก ๋ตํ ์ ์๋ค๋ ๊ฒ์ ์ ์ ๋ก ํ๊ณ ์์ต๋๋ค. ์ด ์ ์ ์ํดํด ์ฃผ์ญ์์ค.
์ด ๊ฒฝ์ฐ ๋ฌธ์ ์ ์ค ํ๋๋, ํด๋ผ์ด์ธํธ๋ฅผ ๋ชจํนํ๊ณ ์๊ธฐ ๋๋ฌธ์ ํด๋ผ์ด์ธํธ๊ฐ ์ ๋๋ก ์ฌ์ฉ๋๊ณ ์๋์ง ์ด๋ป๊ฒ ์ ์ ์๋ค๋ ๊ฒ์ ๋๋ค.
ํด๋ผ์ด์ธํธ๊ฐ window.fetch๋ฅผ ์ ๋๋ก ํธ์ถํ๋์ง ๋จ์ ํ ์คํธ๋ฅผ ํ ์ ์์ง๋ง, ํด๋ผ์ด์ธํธ์ API๊ฐ ์ต๊ทผ์ ๋ฐ์ดํฐ ๋์ ๋ณธ๋ฌธ์ ๋ฐ๋๋ก ๋ณ๊ฒฝ๋์ง ์์๋ค๋ ๊ฒ์ ์ด๋ป๊ฒ ์ ์ ์์๊น์?
TypeScript๋ฅผ ์ฌ์ฉํ๋ฏ๋ก ์ผ๋ถ ๋ฒ๊ทธ ์นดํ ๊ณ ๋ฆฌ๋ ์ ๊ฑฐ๋์์ต๋๋ค! (opens in a new tab) ํ์ง๋ง ๋น์ฆ๋์ค ๋ก์ง ๋ฒ๊ทธ๊ฐ ๋ฐ์ํ ์ ์์ผ๋ฏ๋ก ํด๋ผ์ด์ธํธ๋ฅผ ๋ชจํนํ๋ ๋ฐ ์ฃผ์๊ฐ ํ์ํฉ๋๋ค. ๋ฌผ๋ก E2E ํ ์คํธ๋ฅผ ํตํด ํ์ ์ ์ป์ ์ ์์ง๋ง, ๋ ๋น ๋ฅธ ํผ๋๋ฐฑ ๋ฃจํ๋ฅผ ๊ฐ์ง๊ณ ์๋ ์ด ํ์ ๋ ๋ฒจ์์ ํด๋ผ์ด์ธํธ์ ์ง์ ํธ์ถํ์ฌ ํ์ ์ ์ป๋ ๊ฒ์ด ๋ ๋์ ์๋ ์์ต๋๋ค. ๋ง์ฝ ๊ทธ๋ ๊ฒ ์ด๋ ต์ง ์๋ค๋ฉด์!
ํ์ง๋ง ์ค์ ๋ก โfetchโ ์์ฒญ์ ํ๊ณ ์ถ์ง๋ ์๊ฒ ์ฃ ? ๊ทธ๋ผ โwindow.fetchโ๋ฅผ ๋ชจํนํด ๋ด ์๋ค.
// __tests__/checkout.js
import * as React from "react";
import { render, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
beforeAll(() => jest.spyOn(window, "fetch"));
// jest์ resetMocks๊ฐ "true"๋ก ์ค์ ๋์ด ์๋ค๊ณ ๊ฐ์ ํฉ๋๋ค.
// ์ ๋ฆฌ์ ๋ํด ๊ฑฑ์ ํ ํ์๊ฐ ์์ต๋๋ค.
// ๋ํ `whatwg-fetch`์ ๊ฐ์ ๋ถ๋ฌ์ค๊ธฐ ํด๋ฆฌํ์ ๋ก๋ํ๋ค๊ณ ๊ฐ์ ํฉ๋๋ค.
test('clicking "confirm" submits payment', async () => {
const shoppingCart = buildShoppingCart();
render(<Checkout shoppingCart={shoppingCart} />);
window.fetch.mockResolvedValueOnce({
ok: true,
json: async () => ({ success: true }),
});
userEvent.click(screen.getByRole("button", { name: /confirm/i }));
expect(window.fetch).toHaveBeenCalledWith(
"/checkout",
expect.objectContaining({
method: "POST",
body: JSON.stringify(shoppingCart),
})
);
expect(window.fetch).toHaveBeenCalledTimes(1);
expect(await screen.findByText(/success/i)).toBeInTheDocument();
});
์ด๋ ๊ฒ ํ๋ฉด ์ค์ ๋ก ์์ฒญ์ด ์ด๋ฃจ์ด์ง๊ณ ์์์ ์ข ๋ ํ์ ํ ์ ์์ง๋ง, ์ด ํ ์คํธ์์ ๋ถ์กฑํ ๊ฒ์ 'Content-Type'์ด 'application/json'์ธ์ง ํ์ธํ๋ ๋จ์ธ์ด ์๋ค๋ ๊ฒ์ ๋๋ค. ๊ทธ๊ฒ ์์ด ์ด๋ป๊ฒ ์๋ฒ๊ฐ ๋น์ ์ด ๋ง๋ ์์ฒญ์ ์ธ์ํ ๊ฒ์ด๋ผ๊ณ ํ์ ํ ์ ์๋์? ๊ทธ๋ฆฌ๊ณ ์ฌ๋ฐ๋ฅธ ์ธ์ฆ ์ ๋ณด๊ฐ ์ ์ก๋๊ณ ์๋์ง ์ด๋ป๊ฒ ๋ณด์ฅํ ์ ์๋์?
"์ฐ๋ฆฌ๋ ํด๋ผ์ด์ธํธ ๋จ์ ํ ์คํธ์์ ์ด๋ฏธ ๊ทธ๊ฒ์ ๊ฒ์ฆํ์ด์, Kent. ๋ ๋ฌด์์ ์ํ๋์? ์ ๋ ๋ชจ๋ ๊ณณ์ ๋จ์ธ์ ๋ณต์ฌ/๋ถ์ฌ๋ฃ๊ธฐ ํ๊ณ ์ถ์ง ์์์!" ๋ถ๋ช ํ ๋น์ ์ ์ ์ฅ์ ์ดํดํฉ๋๋ค. ํ์ง๋ง ๋ชจ๋ ํ ์คํธ์์ ๊ทธ ํ์ ์ ์ป์ผ๋ฉด์๋ ๋ชจ๋ ๊ณณ์์ ์ถ๊ฐ์ ์ธ ์์ ์ ํผํ ์ ์๋ ๋ฐฉ๋ฒ์ด ์๋ค๋ฉด ์ด๋จ๊น์? ๊ณ์ ์ฝ์ด๋ณด์ธ์.
์ ๋ฅผ ์ ๋ง ๊ดด๋กญํ๋ ๊ฒ ์ค ํ๋๋ fetch์ ๊ฐ์ ๊ฒ์ ๋ชจํนํ ๋, ๋น์ ์ ํ ์คํธ ๊ณณ๊ณณ์์ ์ ์ฒด ๋ฐฑ์๋๋ฅผ ์ฌ๊ตฌํํ๊ฒ ๋๋ค๋ ๊ฒ์ ๋๋ค. ์ข ์ข ์ฌ๋ฌ ํ ์คํธ์์ ์ด๋ฐ ์ผ์ด ๋ฐ์ํฉ๋๋ค. ํนํ "์ด ํ ์คํธ์์๋ ์ ์์ ์ธ ๋ฐฑ์๋ ์๋ต์ ๊ฐ์ ํฉ๋๋ค"๋ผ๊ณ ํ ๋, ๊ทธ๊ฒ์ ๊ณณ๊ณณ์์ ๋ชจํนํด์ผ๋ง ํ๋ ๊ฒ์ ์ ๋ง ์ง์ฆ๋ฉ๋๋ค. ๊ทธ๋ฐ ๊ฒฝ์ฐ์๋ ์ ๋ง๋ก ๋จ์ง ์ค์ ์์์ด ๋น์ ๊ณผ ์ค์ ๋ก ํ ์คํธํ๋ ค๋ ๊ฒ ์ฌ์ด์ ๋ผ์ด๋ค๊ฒ ๋ฉ๋๋ค.
๊ฒฐ๊ตญ ๋ถ๊ฐํผํ๊ฒ ๋ค์๊ณผ ๊ฐ์ ์๋๋ฆฌ์ค ์ค ํ๋๊ฐ ๋ฐ์ํฉ๋๋ค:
- ํด๋ผ์ด์ธํธ๋ฅผ ๋ชจํนํฉ๋๋ค (์ฒซ ๋ฒ์งธ ํ ์คํธ์์์ฒ๋ผ) ๊ทธ๋ฆฌ๊ณ ๋ช ๊ฐ์ง E2E ํ ์คํธ์ ์์กดํ์ฌ ์ ์ด๋ ๊ฐ์ฅ ์ค์ํ ๋ถ๋ถ์ด ํด๋ผ์ด์ธํธ๋ฅผ ์ฌ๋ฐ๋ฅด๊ฒ ์ฌ์ฉํ๊ณ ์์์ ์ฝ๊ฐ ํ์ ํ ์ ์์ต๋๋ค. ์ด๋ ๋ฐฑ์๋๋ฅผ ๊ฑด๋๋ฆฌ๋ ๊ฒ๋ค์ ํ ์คํธํ ๋๋ง๋ค ๋ฐฑ์๋๋ฅผ ์ฌ๊ตฌํํ๋ ๊ฒฐ๊ณผ๋ฅผ ์ด๋ํฉ๋๋ค. ์ข ์ข ์์ ์ด ์ค๋ณต๋ฉ๋๋ค.
- window.fetch๋ฅผ ๋ชจํนํฉ๋๋ค (๋ ๋ฒ์งธ ํ ์คํธ์์์ฒ๋ผ). ์ด ๋ฐฉ๋ฒ์ ์กฐ๊ธ ๋์ ํธ์ด์ง๋ง, #1๊ณผ ๊ฐ์ ๋ฌธ์ ๋ฅผ ์ผ๋ถ ๊ฒช์ต๋๋ค.
- ๋ชจ๋ ๊ฒ์ ์์ ํจ์๋ค๋ก ๋๋๊ณ ๋ชจ๋๋ฅผ ๋ ๋ฆฝ์ ์ผ๋ก ๋จ์ ํ ์คํธํฉ๋๋ค (์์ฒด์ ์ผ๋ก ๋์์ง ์์ ์ผ) ๊ทธ๋ฆฌ๊ณ ํตํฉ ํ ์คํธ๋ฅผ ํ์ง ์์ต๋๋ค (์ข์ ์ผ์ ์๋๋๋ค).
๊ฒฐ๊ตญ ์ฐ๋ฆฌ๋ ๋ ๋ฎ์ ํ์ ๊ณผ ๋ ๋๋ฆฐ ํผ๋๋ฐฑ ๋ฃจํ, ๋ง์ ์ค๋ณต๋ ์ฝ๋ ๋๋ ์ด๋ค์ ์กฐํฉ์ ์ง๋ฉดํ๊ฒ ๋ฉ๋๋ค.
์ค๋ซ๋์ ๋์๊ฒ ์ ์๋ํ๋ ํ ๊ฐ์ง ๋ฐฉ๋ฒ์ fetch๋ฅผ ํ ํจ์์์ ๋ชจํนํ๋ ๊ฒ์ธ๋ฐ, ์ด๋ ๊ธฐ๋ณธ์ ์ผ๋ก ๋ด๊ฐ ํ ์คํธํ ๋ฐฑ์๋์ ๋ชจ๋ ๋ถ๋ถ์ ์ฌ๊ตฌํํ๋ ๊ฒ์ ๋๋ค. ๋๋ ์ด ๋ฐฉ์์ PayPal์์ ์ฌ์ฉํ๊ณ ๋งค์ฐ ์ ์๋ํ์ต๋๋ค.
์ด๋ฅผ ๋ค์๊ณผ ๊ฐ์ด ์๊ฐํ ์ ์์ต๋๋ค
// add this to your setupFilesAfterEnv config in jest so it's imported for every test file
import * as users from "./users";
async function mockFetch(url, config) {
switch (url) {
case "/login": {
const user = await users.login(JSON.parse(config.body));
return {
ok: true,
status: 200,
json: async () => ({ user }),
};
}
case "/checkout": {
const isAuthorized = user.authorize(config.headers.Authorization);
if (!isAuthorized) {
return Promise.reject({
ok: false,
status: 401,
json: async () => ({ message: "Not authorized" }),
});
}
const shoppingCart = JSON.parse(config.body);
// do whatever other things you need to do with this shopping cart
return {
ok: true,
status: 200,
json: async () => ({ success: true }),
};
}
default: {
throw new Error(`Unhandled request: ${url}`);
}
}
}
beforeAll(() => jest.spyOn(window, "fetch"));
beforeEach(() => window.fetch.mockImplementation(mockFetch));
// __tests__/checkout.js
import * as React from "react";
import { render, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
test('clicking "confirm" submits payment', async () => {
const shoppingCart = buildShoppingCart();
render(<Checkout shoppingCart={shoppingCart} />);
userEvent.click(screen.getByRole("button", { name: /confirm/i }));
expect(await screen.findByText(/success/i)).toBeInTheDocument();
});
๋ด ์ฑ๊ณต ๊ฒฝ๋ก ํ ์คํธ๋ ํน๋ณํ ํ ๊ฒ์ด ์์ต๋๋ค. ์คํจ ์ฌ๋ก์ ๋ํ fetch ๋ชจํน์ ์ถ๊ฐํ ์๋ ์์ง๋ง, ์ด ๋ฐฉ์์ ๋ํด ๋งค์ฐ ๋ง์กฑํ์ต๋๋ค.
์ด ๋ฐฉ์์ ์ฅ์ ์ ๋ด ํ์ ์ด ์ฆ๊ฐํ๊ณ ๋๋ถ๋ถ์ ๊ฒฝ์ฐ์ ์์ฑํด์ผ ํ ํ ์คํธ ์ฝ๋๊ฐ ๋์ฑ ์ค์ด๋ ๋ค๋ ๊ฒ์ ๋๋ค.
๊ทธ๋ฌ๋ ์ค์ msw๋ฅผ ๋ฐ๊ฒฌํ์ต๋๋ค.
msw (opens in a new tab)๋ "Mock Service Worker"์ ์ฝ์์ ๋๋ค. ์๋น์ค ์์ปค๋ Node์์ ์๋ํ์ง ์๊ณ , ๋ธ๋ผ์ฐ์ ๊ธฐ๋ฅ์ ๋๋ค. ํ์ง๋ง msw๋ ํ ์คํธ ๋ชฉ์ ์ผ๋ก Node์์๋ ์ง์ํฉ๋๋ค.
๊ธฐ๋ณธ ์์ด๋์ด๋ ๋ค์๊ณผ ๊ฐ์ต๋๋ค: ๋ชจ๋ ์์ฒญ์ ๊ฐ๋ก์ฑ์ ์ค์ ์๋ฒ์ฒ๋ผ ์ฒ๋ฆฌํ ์ ์๋ ๋ชจ์ ์๋ฒ๋ฅผ ์์ฑํฉ๋๋ค. ์ ๊ฐ์ธ์ ์ธ ๊ตฌํ์์ ์ด๋ json ํ์ผ์ ์ฌ์ฉํ์ฌ "๋ฐ์ดํฐ๋ฒ ์ด์ค"๋ฅผ "์จ๋ฉ"ํ๊ฑฐ๋ faker๋ test-data-bot๊ณผ ๊ฐ์ ๊ฒ์ ์ฌ์ฉํ๋ "๋น๋"๋ฅผ ๋ง๋๋ ๊ฒ์ ์๋ฏธํฉ๋๋ค. ๊ทธ๋ฐ ๋ค์ ์๋ฒ ํธ๋ค๋ฌ(Express API์ ์ ์ฌ)๋ฅผ ๋ง๋ค๊ณ ๊ทธ ๋ชจ์ ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ์ํธ ์์ฉํฉ๋๋ค. ์ด ๋ฐฉ๋ฒ์ ๋ด ํ ์คํธ๋ฅผ ๋น ๋ฅด๊ณ ์ฝ๊ฒ ์์ฑํ ์ ์๊ฒ ํด์ค๋๋ค(์ผ๋จ ์ค์ ์ ๋ง์น๋ฉด).
์ด์ ์ nock๊ณผ ๊ฐ์ ๊ฒ์ ์ฌ์ฉํ์ฌ ์ด๋ฌํ ์์ ์ ํด๋ณธ ์ ์ด ์์ ์ ์์ต๋๋ค. ๊ทธ๋ฌ๋ msw์ ๋ํด ๋ฉ์ง ์ (๊ทธ๋ฆฌ๊ณ ๋์ค์ ์ธ ์๋ ์๋ ๊ฒ)์ ๊ฐ๋ฐ ์ค์ ๋ธ๋ผ์ฐ์ ์์๋ ๋์ผํ "์๋ฒ ํธ๋ค๋ฌ"๋ฅผ ์ฌ์ฉํ ์ ์๋ค๋ ๊ฒ์ ๋๋ค. ์ด๋ ๋ช ๊ฐ์ง ํฐ ์ด์ ์ ๊ฐ์ง๊ณ ์์ต๋๋ค:
- ์๋ํฌ์ธํธ๊ฐ ์ค๋น๋์ง ์์์ ๋
- ์๋ํฌ์ธํธ๊ฐ ๊ณ ์ฅ ๋ฌ์ ๋
- ์ธํฐ๋ท ์ฐ๊ฒฐ์ด ๋๋ฆฌ๊ฑฐ๋ ์กด์ฌํ์ง ์์ ๋
Mirage (opens in a new tab)์ ๋ํด ๋ค์ด๋ณด์ จ์ ์ ์๋๋ฐ, ๋ง์ ๋ถ๋ถ์์ ๋น์ทํ ์ผ์ ํฉ๋๋ค. ๊ทธ๋ฌ๋ (ํ์ฌ๋ก์๋) mirage๋ ํด๋ผ์ด์ธํธ์์ ์๋น์ค ์์ปค๋ฅผ ์ฌ์ฉํ์ง ์์ผ๋ฉฐ, msw๋ฅผ ์ค์นํ๋์ง ์ฌ๋ถ์ ๊ด๊ณ์์ด ๋คํธ์ํฌ ํญ์ด ๋์ผํ๊ฒ ์๋ํ๋ค๋ ์ ์ด ๋ง์์ ๋ญ๋๋ค. ๊ทธ ์ฐจ์ด์ ์ ๋ํด ๋ ์์๋ณด์ธ์.
// server-handlers.js
// this is put into here so I can share these same handlers between my tests
// as well as my development in the browser. Pretty sweet!
import { rest } from "msw"; // msw supports graphql too!
import * as users from "./users";
const handlers = [
rest.get("/login", async (req, res, ctx) => {
const user = await users.login(JSON.parse(req.body));
return res(ctx.json({ user }));
}),
rest.post("/checkout", async (req, res, ctx) => {
const user = await users.login(JSON.parse(req.body));
const isAuthorized = user.authorize(req.headers.Authorization);
if (!isAuthorized) {
return res(ctx.status(401), ctx.json({ message: "Not authorized" }));
}
const shoppingCart = JSON.parse(req.body);
// do whatever other things you need to do with this shopping cart
return res(ctx.json({ success: true }));
}),
];
export { handlers };
// test/server.js
import { rest } from "msw";
import { setupServer } from "msw/node";
import { handlers } from "./server-handlers";
const server = setupServer(...handlers);
export { server, rest };
(โ 24๋
5์ ๊ธฐ์ค import {rest} from 'msw'
๋์ import {http} from 'msw'
์ฌ์ฉ)
// test/setup-env.js
// add this to your setupFilesAfterEnv config in jest so it's imported for every test file
import { server } from "./server.js";
beforeAll(() => server.listen());
// if you need to add a handler after calling setupServer for some specific test
// this will remove that handler for the rest of them
// (which is important for test isolation):
afterEach(() => server.resetHandlers());
afterAll(() => server.close());
ํ ์คํธ ์ฝ๋๋ ๋ค์๊ณผ ๊ฐ์ต๋๋ค.
// __tests__/checkout.js
import * as React from "react";
import { render, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
test('clicking "confirm" submits payment', async () => {
const shoppingCart = buildShoppingCart();
render(<Checkout shoppingCart={shoppingCart} />);
userEvent.click(screen.getByRole("button", { name: /confirm/i }));
expect(await screen.findByText(/success/i)).toBeInTheDocument();
});
fetch ๋ชจํน๋ณด๋ค ์ด ๋ฐฉ๋ฒ์ด ๋ ๋ง์กฑ์ค๋ฌ์ด ์ด์ ๋ ๋ค์๊ณผ ๊ฐ์ต๋๋ค.
- ๊ฐ์ ธ์ค๊ธฐ ์๋ต ์์ฑ๊ณผ ํค๋์ ๊ตฌํ ์ธ๋ถ ์ฌํญ์ ๋ํด ๊ฑฑ์ ํ ํ์๊ฐ ์์ต๋๋ค.
- **โfetchโ**๋ฅผ ํธ์ถํ๋ ๋ฐฉ๋ฒ์ ๋ฌธ์ ๊ฐ ์์ผ๋ฉด ์๋ฒ ํธ๋ค๋ฌ๊ฐ ํธ์ถ๋์ง ์๊ณ ๋ด ํ ์คํธ๊ฐ (์ ํํ๊ฒ) ์คํจํ์ฌ, ๊ฒฐํจ ์๋ ์ฝ๋๋ฅผ ๋ฐฐํฌํ๋ ๊ฒ์ ๋ฐฉ์งํ ์ ์์ต๋๋ค.
- ์ด์ ๋์ผํ ์๋ฒ ํธ๋ค๋ฌ๋ฅผ ๊ฐ๋ฐ ์ค์๋ ์ฌ์ฌ์ฉํ ์ ์์ต๋๋ค!
์ฝ๋ก์ผ์ด์ ๊ณผ ์ค๋ฅ/์ฃ์ง ์ผ์ด์ค ํ ์คํธ์ ๊ดํ์ฌ
์ด ์ ๊ทผ ๋ฐฉ์์ ๋ํ ํ๋์ ํฉ๋ฆฌ์ ์ธ ์ฐ๋ ค๋ ๋ชจ๋ ์๋ฒ ํธ๋ค๋ฌ๋ฅผ ํ ๊ณณ์ ๋ชจ์๋๊ณ , ๊ทธ ์๋ฒ ํธ๋ค๋ฌ์ ์์กดํ๋ ํ ์คํธ๋ค์ด ์ ํ ๋ค๋ฅธ ํ์ผ์ ์์นํ๊ฒ ๋์ด ์ฝ๋ก์ผ์ด์ ์ ์ด์ ์ ์์ด๋ฒ๋ฆฐ๋ค๋ ๊ฒ์ ๋๋ค.
์ฐ์ , ์ค์ํ๊ณ ํ ์คํธ์ ํน์ ํ ์์๋ค๋ง ์ฝ๋ก์ผ์ด์ ํ๋ ๊ฒ์ด ์ข์ต๋๋ค. ๋ชจ๋ ์ค์ ์ ๊ฐ ํ ์คํธ๋ง๋ค ์ค๋ณตํด์ ๊ฐ์ง ํ์๋ ์์ต๋๋ค. ์ ์ผํ ๋ถ๋ถ๋ง ํ์ํฉ๋๋ค. ๊ทธ๋์ "์ฑ๊ณต ๊ฒฝ๋ก"์ ํด๋นํ๋ ๊ฒ๋ค์ ์ผ๋ฐ์ ์ผ๋ก ์ค์ ํ์ผ์ ํฌํจ์์ผ ํ ์คํธ ์์ฒด์์๋ ์ ๊ฑฐํ๋ ๊ฒ์ด ๋ ๋ซ์ต๋๋ค. ๊ทธ๋ ์ง ์์ผ๋ฉด ๋๋ฌด ๋ง์ ์ก์์ด ์๊ฒจ ์ค์ ๋ก ๋ฌด์์ด ํ ์คํธ๋๊ณ ์๋์ง ๋ถ๋ฆฌํ๊ธฐ ์ด๋ ต์ต๋๋ค.
๊ทธ๋ ๋ค๋ฉด ์ฃ์ง ์ผ์ด์ค์ ์ค๋ฅ์ ๋ํด์๋ ์ด๋จ๊น์? ์ด ๊ฒฝ์ฐ, MSW๋ ํ ์คํธ ์ค์ ์ถ๊ฐ์ ์ธ ์๋ฒ ํธ๋ค๋ฌ๋ฅผ ๋ฐํ์์ ์ถ๊ฐํ ์ ์๋ ๊ธฐ๋ฅ์ ์ ๊ณตํ๋ฉฐ, ์๋ฒ๋ฅผ ์๋์ ํธ๋ค๋ฌ๋ก ์ฌ์ค์ ํ์ฌ (๋ฐํ์ ํธ๋ค๋ฌ๋ฅผ ํจ๊ณผ์ ์ผ๋ก ์ ๊ฑฐํ์ฌ) ํ ์คํธ ๊ฒฉ๋ฆฌ๋ฅผ ์ ์งํฉ๋๋ค. ์๋ฅผ ๋ค์ด ๋ค์๊ณผ ๊ฐ์ต๋๋ค:
// __tests__/checkout.js
import * as React from "react";
import { server, rest } from "test/server";
import { render, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
// happy path test, no special server stuff
test('clicking "confirm" submits payment', async () => {
const shoppingCart = buildShoppingCart();
render(<Checkout shoppingCart={shoppingCart} />);
userEvent.click(screen.getByRole("button", { name: /confirm/i }));
expect(await screen.findByText(/success/i)).toBeInTheDocument();
});
// edge/error case, special server stuff
// note that the afterEach(() => server.resetHandlers()) we have in our
// setup file will ensure that the special handler is removed for other tests
test("shows server error if the request fails", async () => {
const testErrorMessage = "THIS IS A TEST FAILURE";
server.use(
rest.post("/checkout", async (req, res, ctx) => {
return res(ctx.status(500), ctx.json({ message: testErrorMessage }));
})
);
const shoppingCart = buildShoppingCart();
render(<Checkout shoppingCart={shoppingCart} />);
userEvent.click(screen.getByRole("button", { name: /confirm/i }));
expect(await screen.findByRole("alert")).toHaveTextContent(testErrorMessage);
});
๋ฐ๋ผ์ ์ฝ๋ก์ผ์ด์ ์ด ํ์ํ ๊ณณ์๋ ์ฝ๋ก์ผ์ด์ ์, ์ถ์ํ๊ฐ ํ์ํ ๊ณณ์๋ ์ถ์ํ๋ฅผ ์ฌ์ฉํ ์ ์์ต๋๋ค.
๊ฒฐ๋ก
**โmswโ**์ ๊ด๋ จํ์ฌ ํ ์ผ์ด ๋ถ๋ช ํ ๋ ๋ง์ง๋ง, ์ผ๋จ ์ฌ๊ธฐ์ ๋ง๋ฌด๋ฆฌํ๊ฒ ์ต๋๋ค. **โmswโ**๋ฅผ ์ค์ ๋ก ๋ณด๊ณ ์ถ๋ค๋ฉด, ์ ๊ฐ ์งํํ๋ 4๋ถ์ ์ํฌ์ "Build React Apps" (EpicReact.Dev์ ํฌํจ)์์ ์ฌ์ฉ๋๋ฉฐ, GitHub์์ ๋ชจ๋ ์๋ฃ๋ฅผ ์ฐพ์๋ณผ ์ ์์ต๋๋ค.
์ด ํ ์คํธ ๋ฐฉ๋ฒ์ ์ ๋ง ๋ฉ์ง ์ธก๋ฉด ์ค ํ๋๋ ๊ตฌํ ์ธ๋ถ ์ฌํญ์ผ๋ก๋ถํฐ ๋ฉ๋ฆฌ ๋จ์ด์ ธ ์๊ธฐ ๋๋ฌธ์, ์๋นํ ๋ฆฌํฉํ ๋ง์ ์ํํ ์ ์์ผ๋ฉฐ, ํ ์คํธ๊ฐ ์ฌ์ฉ์ ๊ฒฝํ์ ๊นจ๋จ๋ฆฌ์ง ์์๋ค๋ ํ์ ์ ์ค ์ ์๋ค๋ ๊ฒ์ ๋๋ค. ๋ฐ๋ก ์ด๊ฒ์ด ํ ์คํธ๊ฐ ์กด์ฌํ๋ ์ด์ ์ ๋๋ค! ์ด๋ฐ ์ผ์ด ์ผ์ด๋ ๋ ์ ๋ง ์ข์ต๋๋ค:
Kent C. Dodds
@kentcdodds
์ต๊ทผ์ ๋ด ์ฑ์์ ์ธ์ฆ ๋ฐฉ์์ ์์ ํ ๋ณ๊ฒฝํ๋๋ฐ, ํ ์คํธ ์ ํธ๋ฆฌํฐ์ ์ฝ๊ฐ์ ์์ ๋ง ํ์ํ๊ณ , ๋ชจ๋ ํ ์คํธ(๋จ์, ํตํฉ, E2E)๊ฐ ํต๊ณผํ์ฌ ์ฌ์ฉ์ ๊ฒฝํ์ด ๋ณ๊ฒฝ์ ์ํฅ์ ๋ฐ์ง ์์๋ค๋ ํ์ ์ ์ฃผ์์ต๋๋ค. ๋ฐ๋ก ์ด๊ฒ์ด ํ ์คํธ๊ฐ ์กด์ฌํ๋ ์ด์ ์ฃ !
Dillon
@d11erh ์ค๋์ React ์ปดํฌ๋ํธ๋ฅผ ๋ฆฌํฉํ ๋งํ๋ ๋ฐ ํ๋ฃจ๋ฅผ ๋ณด๋์ต๋๋ค. react-testing-library๋ก ์ ์์ฑ๋ ํ ์คํธ(๊ฐ์ฌํฉ๋๋ค @kentcdodds)๋ ํฐ ํ์ ์ ์ฃผ์๊ณ ๋ฏธ๋ฌํ ์ค๋ฅ๋ฅผ ์ก๋ ๋ฐ ๋์์ด ๋์์ต๋๋ค.
๊ฒฐ๋ก : ์ข์ ๋จ์ ํ ์คํธ๋ ์ ๋ง ์ค์ํฉ๋๋ค!