๐ MSW(Mock Service Worker)
โ๏ธ MSW
๊ธฐ์กด API ๋ชจํน ๋ผ์ด๋ธ๋ฌ๋ฆฌ ๋๋น MSW์ ๊ฐ์ฅ ํฐ ๊ฐ์ ์ ๋ชจํน์ด ๋คํธ์ํฌ ๋จ์์ ์ผ์ด๋๊ธฐ ๋๋ฌธ์ ํ๋ฐํธ์ค๋ ์ฝ๋๋ฅผ ์ค์ ๋ฐฑ์ค๋ API์ ๋คํธ์ํฌ ํต์ ํ๋ ๊ฒ๊ณผ ํฌ๊ฒ ๋ค๋ฅด์ง ์๊ฒ ์์ฑํ ์ ์๋ค๋ ๊ฒ์ ๋๋ค. ์ด ๋ง์ ๋์ค์ ๊ฐ์ง API๋ฅผ ์ค์ API๋ก ๋์ฒดํ๋ ๊ฒ์ด ์ฝ๋ค๋ ๋ป์ด๋ฉฐ ๊ทธ๋งํผ ํ๋ฐํธ์ค๋ ํ๋ก์ ํธ์ ๊ฐ๋ฐ ์์ฐ์ฑ์ด ์ฌ๋ผ๊ฐ๋ ๊ฒ์ ์๋ฏธํฉ๋๋ค.
โ๏ธ MSW ๋ผ์ด๋ธ๋ฌ๋ฆฌ ์ค์น
1
$ npm i -D msw
โ๏ธ ์๋น์ค ์์ปค ์ฝ๋ ์์ฑํ๊ธฐ
MSW๋ ๋ธ๋ผ์ฐ์ ์์ ์๋น์ค ์์ปค๋ฅผ ํตํด์ ์๋ํ๊ธฐ ๋๋ฌธ์ ์๋น์ค ์์ปค ๋ฑ๋ก์ ์ํ ๊ธฐ๋ณธ์ ์ธ ์ฝ๋๊ฐ ํ์ํ๋ฐ์. ๋คํํ๋ ์๋น์ค ์์ปค์ ๋ํด์ ์ ๋ชฐ๋ผ๋ MSW์์ ์ ๊ณตํ๋ CLI ๋๊ตฌ๋ฅผ ์ฌ์ฉํ๋ฉด ์ด ์ฝ๋๋ฅผ ์ฝ๊ฒ ๋ง๋ค์ด ๋ผ ์ ์์ต๋๋ค.
1
$ npx msw init public/ --save
โ๏ธ ์์ฒญ ํธ๋ค๋ฌ ์์ฑ
๊ฐ์ง API๋ฅผ ๊ตฌํํ๋ ค๋ฉด ์์ฒญ์ด ๋ค์ด์์ ๋ ์์์ ์๋ต์ ํด์ฃผ๋ ํธ๋ค๋ฌ(handler) ์ฝ๋๋ฅผ ์์ฑํด์ผํฉ๋๋ค.
src/mocks/hander.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import { rest } from "msw";
const todos = ["๋จน๊ธฐ", "์๊ธฐ", "๋๊ธฐ"];
export const handlers = [
// ํ ์ผ ๋ชฉ๋ก
rest.get("/todos", (req, res, ctx) => {
return res(ctx.status(200), ctx.json(todos));
}),
// ํ ์ผ ์ถ๊ฐ
rest.post("/todos", (req, res, ctx) => {
todos.push(req.body);
return res(ctx.status(201));
}),
];
โ๏ธ ์๋น์ค ์์ปค ์์ฑ
๋ค์์ผ๋ก msw ๋ชจ๋์์ ์ ๊ณตํ๋ setupWorker() ํจ์๋ฅผ ์ฌ์ฉํด์ ์๋น์ค ์์ปค๋ฅผ ์์ฑํ๊ฒ ์ต๋๋ค. ์์์ ์์ฑํ ์์ฒญ ํธ๋ค๋ฌ ์ฝ๋๋ฅผ ๋ถ๋ฌ์์ ๊ทธ๋๋ก setupWorker() ํจ์์ ์ธ์๋ก ๋๊ฒจ์ฃผ๋ฉด ๋ฉ๋๋ค.
src/mocks/worker.js
1
2
3
4
import { setupWorker } from "msw";
import { handlers } from "./handlers";
export const worker = setupWorker(...handlers);
โ๏ธ ์๋น์ค ์์ปค ์ฝ์
์ด์ ์๋น์ค ์์ปค๋ฅผ ๊ตฌ๋ํ๋ ์ฝ๋๋ฅผ ์ ํ๋ฆฌ์ผ์ด์ ์ ์ง์ ์์ (entrypoint)์ ์ฝ์ ์ ํด๋ณด๊ฒ ์ต๋๋ค. ์๋ฅผ ๋ค์ด, Create React App์ผ๋ก ๋ง๋ ์ ํ๋ฆฌ์ผ์ด์ ์ ๊ฒฝ์ฐ, src/index.js ํ์ผ์ ๋ค์๊ณผ ๊ฐ์ด ์์ ํด์ฃผ๋ฉด ๋ฉ๋๋ค.
src/index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import App from "./App";
//์ฌ๊ธฐ ์ถ๊ฐ
import { worker } from "./mocks/worker";
if (process.env.NODE_ENV === "development") {
worker.start();
}
const rootElement = document.getElementById("root");
const root = createRoot(rootElement);
root.render(
<StrictMode>
<App />
</StrictMode>
);
โ๏ธ ์๋น์ค ์์ปค ํ ์คํธ
์ด์ ์ ํ๋ฆฌ์ผ์ด์ ์ ๊ตฌ๋ ํ์ ๋ธ๋ผ์ฐ์ ์์ ์ด๋ฉด ์ฝ์์ ๋ค์๊ณผ ๊ฐ์ด ๋ชจํน์ด ํ์ฑํ๋์๋ค๋ ๋ฉ์ธ์ง๊ฐ ์ถ๋ ฅ๋ ๊ฒ ์ ๋๋ค.
1
[MSW] Mocking enabled.
์ด์ fetch() ํจ์๋ฅผ ์ฌ์ฉํด์ ์ ๋ง๋ก GET /todos์ ์์ฒญ์ ๋ณด๋ด๋ฉด ๊ฐ์ง ์๋ต์ด ์ค๋์ง ํ์ธํด๋ณด๊ฒ ์ต๋๋ค.
1
2
3
fetch("/todos")
.then((response) => response.json())
.then((data) => console.log(data));
1
2
[MSW] 22:09:24 GET /todos (200 OK)
['๋จน๊ธฐ', '์๊ธฐ', '๋๊ธฐ']
โ๏ธ ํ ์คํธ ์๋ฒ ์์ฑ
MSW๋ ๊ฐ๋ฐ์์ ๋ฟ๋ง ์๋๋ผ ํ ์คํธ์์๋ ํ์ฉํ ์ ์๋ค๊ณ ๋ง์๋๋ ธ์ฃ ? msw/node ๋ชจ๋์ setupServer() ํจ์๋ฅผ ์ด์ฉํ๋ฉด ๊ฐ๋จํ๊ฒ ํ ์คํธ์ฉ API ์๋ฒ๋ฅผ ๋ง๋ค ์ ์๋๋ฐ์. ์์์ ์์ฑํ ๊ฐ์ง ์๋ต์ ํด์ฃผ๋ ์์ฒญ ํธ๋ค๋ฌ๋ฅผ ๊ทธ๋๋ก ์ฌํ์ฉํ๋ฉด ๋ฉ๋๋ค.
src/mocks/server.js
1
2
3
4
import { setupServer } from "msw/node";
import { handlers } from "./handlers";
export const server = setupServer(...handlers);
โ๏ธ ํ ์คํธ ์๋ฒ ์ค์
ํ ์คํธ ์คํ ์ ์ ๊ฐ์ง API ์๋ฒ๋ฅผ ์ฌ๋ ธ๋ค๊ฐ ํ ์คํธ ์คํ ํ์ ๋ด๋ฆด ์ ์๋๋ก Jest ์ค์ ์ ํด์ค๋๋ค. ์๋ฅผ ๋ค์ด, Create React App์ผ๋ก ๋ง๋ ํ๋ก์ ํธ์์๋ setupTests.js์ ๋ค์ ์ฝ๋๋ฅผ ์ถ๊ฐํด์ฃผ๋ฉด ๋ฉ๋๋ค.
src/setupTests.js
1
2
3
4
5
6
import "@testing-library/jest-dom";
import { server } from "./mocks/server";
beforeAll(() => server.listen());
afterEach(() => server.resetHandlers());
afterAll(() => server.close());
src/App.test.jax
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import { render, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import App from "./App";
test("renders todos", async () => {
render(<App />);
const listitems = await screen.findAllByRole("listitem");
expect(listitems).toHaveLength(3);
userEvent.type(screen.getByRole("textbox"), "๊ณต๋ถํ๊ธฐ");
userEvent.click(screen.getByRole("button"));
expect(await screen.findByText("๊ณต๋ถํ๊ธฐ")).toBeInTheDocument();
});