Home MSW
Post
Cancel

MSW

๐Ÿ“Œ 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();
});

https://www.daleseo.com/mock-service-worker/

This post is licensed under CC BY 4.0 by the author.