1. Mock Server ๐Ÿ‘ฉโ€๐Ÿ’ป

ํ”„๋ก ํŠธ์—”๋“œ๋‚˜ ์•ฑ ๊ฐœ๋ฐœ์„ ํ•˜๋‹ค ๋ณด๋ฉด ํ•ญ์ƒ ๋ฌธ์ œ๊ฐ€ API ๊ฐœ๋ฐœ์ด ๋˜์–ด์žˆ์–ด์•ผ ์‹ค์ œ ๊ฐœ๋ฐœ์„ ์ง„ํ–‰ํ•  ์ˆ˜ ์žˆ๋‹ค๋Š” ๊ฒƒ์ด๋‹ค. ํšŒ์‚ฌ์—์„œ ๊ทผ๋ฌดํ•  ๋•Œ ํ’€์Šคํƒ์œผ๋กœ ์ผํ–ˆ๊ธฐ ๋•Œ๋ฌธ์— ๋‚ด๊ฐ€ ์„œ๋ฒ„ DB ํ…Œ์ด๋ธ”๋„ ๋งŒ๋“ค๊ณ , ์„œ๋ฒ„ API ๋„ ๋งŒ๋“ค๊ณ , ํ”„๋ก ํŠธ์—”๋“œ ํ™”๋ฉด๋„ ๋งŒ๋“ค์–ด์„œ ์‚ฌ์‹ค DB -> ์„œ๋ฒ„ -> ํ™”๋ฉด ์ˆœ์œผ๋กœ ๋งŒ๋“ค๋ฉด ๋์ง€๋งŒ ๋Œ€๋ถ€๋ถ„์€ ํšŒ์‚ฌ๊ฐ€ ํฌ๊ณ  ๋ถ„์—…ํ™”๊ฐ€ ๋˜๋ฉด ๊ฐœ๋ฐœ ํฌ์ง€์…˜์ด ๋‚˜๋‰˜๊ฒŒ ๋˜์–ด API ๊ฐ€ ๋‚˜์˜ค๊ธฐ๋งŒ ๊ธฐ๋‹ค๋ฆฌ๋Š” ์ƒํ™ฉ์ด ์ƒ๊ธฐ๊ฒŒ ๋œ๋‹ค.

์ด๊ฑธ ๊ฒฝํ—˜ํ•œ ๊ฒŒ ๋‚˜๋Š” A -> B -> C ์ˆœ์„œ๋กœ ๊ฐœ๋ฐœํ•˜๋ ค๊ณ  ํ–ˆ๋Š”๋ฐ ์•ฑ ๊ฐœ๋ฐœ์ž๊ฐ€ B -> C -> A ์ˆœ์œผ๋กœ ํ•ด๋‹ฌ๋ผ๊ณ โ€ฆ API ๊ฐ€ ๋‚˜์™€์•ผ ๊ฐœ๋ฐœํ•œ๋‹ค๊ณ โ€ฆ ์ด๋Ÿด๋•Œ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ๊ฒŒ Mock Server ๋‹ค.

์‚ฌ์‹ค Mocking ์„ ์œ„ํ•œ ์„œ๋ฒ„๋Š” Spring ๋Œ€์‹  Express ๋กœ ๋„์šฐ๋ฉด ์ƒ๊ฐ๋ณด๋‹ค ์‰ฝ๊ฒŒ ๋„์šธ ์ˆ˜ ์žˆ๋‹ค. ํ•˜์ง€๋งŒ ๋Œ€๋ถ€๋ถ„ ์ด๊ฑธ ์•ˆ ํ•˜์ง€โ€ฆ ์•„๋ฌดํŠผ, Express ๋กœ Mock Server ๋ฅผ ๋„์šฐ๋Š” ๊ฒƒ์ด ๊ท€์ฐฎ๋‹ค๋ฉด ์‚ฌ์‹ค Postman ์„ ์‚ฌ์šฉํ•ด์„œ๋„ Mock Server ๋ฅผ ๋„์šธ ์ˆ˜ ์žˆ๋‹ค(Postman - Mock servers). ๋Œ€๋ถ€๋ถ„ Postman ์„ API ํ…Œ์ŠคํŠธ ์šฉ์œผ๋กœ ์‚ฌ์šฉํ•˜์ง€๋งŒ, ์‚ฌ์‹ค Postman ์€ ์ด๋ฅผ ์—ญ์œผ๋กœ API ๋ฅผ ๋งŒ๋“ค์–ด ์ƒํƒœ๋ฅผ ์ €์žฅํ•˜๊ณ , Mocking ํ•˜๋Š” ๊ฒƒ์„ ์ œ๊ณตํ•  ์ˆ˜ ์žˆ๋Š” ๊ธฐ๋Šฅ์„ ์ œ๊ณตํ•˜๊ณ  ์žˆ๋‹ค. JSON ์‘๋‹ต ๋ฟ ์•„๋‹ˆ๋ผ Postman ์ด ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ๋‹ค์–‘ํ•œ ์‘๋‹ต์„ ๋ชจ๋‘ ์ง€์›ํ•˜๋ฉฐ, Binary ๋„ ๊ฐ€๋Šฅํ•ด ํŒŒ์ผ ํ…Œ์ŠคํŠธ ์—ญ์‹œ ๊ฐ€๋Šฅํ•˜๋‹ค. ๋‹จ์ ์ด๋ผ๋ฉด Postman ์„œ๋ฒ„์™€ ์ธํ„ฐ๋„ท ์—ฐ๊ฒฐ์ด ํ•„์š”ํ•˜๊ธฐ ๋•Œ๋ฌธ์— Offline ์—์„œ๋Š” ์‚ฌ์šฉ์ด ๋ถˆ๊ฐ€๋Šฅํ•˜๋‹ค๋Š” ๊ฒƒ ์ •๋„? ๊ฒŒ๋‹ค๊ฐ€ ํšŒ์‚ฌ์—์„œ ์‚ฌ์šฉํ•  ๊ฒฝ์šฐ ์•„๋งˆ๋„ ์ผ์ •๋Ÿ‰ ์ดˆ๊ณผ ์‚ฌ์šฉ์‹œ ๋น„์šฉ ๋ถ€๊ณผ(?) ์ •๋„์ด์ง€ ์•Š์„๊นŒ?

์•„๋ฌดํŠผ Node.js ์—๋Š” ์ง์ ‘ Express ์„œ๋ฒ„๋ฅผ ๋งŒ๋“ค์ง€ ์•Š์•„๋„ ์‰ฝ๊ฒŒ Mock Server ๋ฅผ ๋งŒ๋“ค ์ˆ˜ ์žˆ๋Š” ๋‹ค์–‘ํ•œ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๊ฐ€ ์กด์žฌํ•˜๋Š” ๋ฐ, ๊ทธ ์ค‘ ํ”„๋ก ํŠธ์—”๋“œ์—์„œ ๋งŽ์ด ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด Mock Service Worker ๋‹ค. MSW ๋Š” Node.js ํ†ตํ•ฉ ๋ฟ ์•„๋‹ˆ๋ผ ๋ธŒ๋ผ์šฐ์ €์˜ Service Worker ์™€ ํ†ตํ•ฉ์ด ๊ฐ€๋Šฅํ•˜๋ฉฐ, React Native ์™€์˜ ํ†ตํ•ฉ ์—ญ์‹œ ์ง€์›ํ•˜๋Š” ๊ต‰์žฅํžˆ ๋†€๋ผ์šด ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์ด๋‹ค.


2. Install ๐Ÿ‘ฉโ€๐Ÿ’ป

1. npm install

npm i -D msw

์„ค์น˜ ์ดํ›„ ์ฝ”๋“œ ์ž‘์„ฑ์‹œ ๋ฒ„์ „์— ๋”ฐ๋ผ ๋ณ€๊ฒฝ๋˜๋Š” ๋ถ€๋ถ„์ด ์žˆ์œผ๋ฏ€๋กœ ๊ฐ€๊ธ‰์  MSW ๋ฅผ ํ•œ ๋ฒˆ ํ™•์ธํ•˜๋Š” ๊ฒƒ์ด ์ข‹๋‹ค.

2. Browser Integration

1 ) Copy the worker script

๋‹ค์Œ ๋ช…๋ น์–ด๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด Service Worker ์‚ฌ์šฉ์„ ์œ„ํ•œ ์Šคํฌ๋ฆฝํŠธ๋ฅผ ์ž๋™์œผ๋กœ ์„ค์น˜ํ•ด์ค€๋‹ค.

npx msw init <PUBLIC_DIR>

์ผ๋ฐ˜์ ์œผ๋กœ public์ด๋ผ๋Š” ๋””๋ ‰ํ† ๋ฆฌ๋ฅผ ์‚ฌ์šฉํ•˜๋ฏ€๋กœ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์ž‘์„ฑํ•˜๋ฉด ๋œ๋‹ค.

npx msw init public

์ด์ œ public ์— mockServiceWorker.js๊ฐ€ ์ƒ์„ฑ๋œ๋‹ค.


2 ) Setup

  • src/mocks/browser.ts
import { setupWorker } from "msw/browser";
import { handlers } from "./handlers";

export const worker = setupWorker(...handlers);

์•„์ง import { handlers } from "./handlers"; ๋ถ€๋ถ„์„ ์ƒ์„ฑํ•˜์ง€ ์•Š์•„ ์—๋Ÿฌ๊ฐ€ ๋œฐ ๊ฒƒ์ด์ง€๋งŒ ๋ฌด์‹œํ•˜์ž. ๋ฐ”๋กœ ๋‹ค์Œ ์ฑ•ํ„ฐ์ธ Make handlers ์—์„œ ์ž‘์„ฑํ•  ๊ฒƒ์ด๋‹ค.


3 ) Conditionally enable mocking

  • src/index.tsx
import React from 'react'
import ReactDOM from 'react-dom'
import { App } from './App'
 
async function enableMocking() {
  if (process.env.NODE_ENV !== 'development') return
 
  const { worker } = await import('./mocks/browser')
 
  // `worker.start()` returns a Promise that resolves
  // once the Service Worker is up and ready to intercept requests.
  return worker.start()
}
 
enableMocking().then(() => {
  ReactDOM.render(<App />, rootElement)
})

3. Make handlers ๐Ÿ‘ฉโ€๐Ÿ’ป

1. Group request handlers

๋‹จ์ผ ํ•ธ๋“ค๋Ÿฌ๋ฉด src/mocks/handlers.ts ํ•˜๋‚˜๋งŒ ๋งŒ๋“ค๋ฉด ๋˜๋Š”๋ฐ, ๋ณดํ†ต API ๋ฅผ ๋งŽ์ด ๋งŒ๋“ค๋‹ค ๋ณด๋ฉด ์ฝ”๋“œ๊ฐ€ ํ˜ผ์žกํ•ด์ง€๊ธฐ ๋•Œ๋ฌธ์— ๋ฐฑ์—”๋“œ ์„œ๋ฒ„๋ฅผ ๋งŒ๋“ ๋‹ค๋Š” ์ƒ๊ฐ์œผ๋กœ Endpoint ๋ฅผ ๊ด€์‹ฌ์‚ฌ ๊ธฐ์ค€์œผ๋กœ ๋‚˜๋ˆ„์–ด handlers ๋ฅผ ๊ฐ๊ฐ์˜ ํŒŒ์ผ๋กœ ๊ตฌ์„ฑํ•ด src/mocks/handlers/index.ts ํ•˜๋‚˜๋กœ ๋ชจ์œผ๋Š” ๊ฒƒ์ด ์ข‹๋‹ค.

MSW Tree

์œ„ ์‚ฌ์ง„์—์„œ db๋ผ๊ณ  ๋ถ„๋ฆฌํ•ด ๋†“์€ ์ฝ”๋“œ๋Š” ๊ณต์‹ ๋ฌธ์„œ์˜ ํŠœํ† ๋ฆฌ์–ผ์—๋Š” ๋‚˜์˜ค์ง€ ์•Š์€ ๋ถ€๋ถ„์œผ๋กœ ํ•„์ˆ˜ ๊ตฌ์„ฑ ์š”์†Œ๋Š” ์•„๋‹ˆ๋‹ค. ๋‹ค๋งŒ handlers ์•ˆ์— ๊ธฐ๋ณธ์œผ๋กœ ์ œ๊ณตํ•  ๋ฐ์ดํ„ฐ๋“ค์ด ํ•จ๊ป˜ ์กด์žฌํ•˜๋ฉด ๋ผ์ธ ์ˆ˜๊ฐ€ ๋„ˆ๋ฌด ๋งŽ์•„์ ธ ๊ฐ€๋…์„ฑ์ด ์ข‹์ง€ ๋ชปํ•œ ๋ฌธ์ œ๊ฐ€ ์žˆ์–ด ๋ถ„๋ฆฌํ–ˆ๋‹ค.

  • src/mocks/handlers/index.ts
import { HttpResponse } from "msw";
import { handlers as optionsHandlers } from "./options";
import { handlers as orderHandlers } from "./order";
import { handlers as productsHandlers } from "./products";
import { handlers as staticHandlers } from "./static";

export const handlers = [
  ...optionsHandlers,
  ...orderHandlers,
  ...productsHandlers,
  ...staticHandlers,
];

interface Params {
  request: Request;
  params: {
    [key: string]: unknown;
  };
  cookies: {
    [key: string]: unknown;
  };
}

export interface HttpResolver {
  (params: Params): HttpResponse | Promise<HttpResponse>;
}

์ด index.ts๊ฐ€ ๊ฐ๊ฐ์˜ handlers๋ฅผ ํ•˜๋‚˜๋กœ ๋ชจ์„ ๊ฒƒ์ด๋‹ค. Params์™€ HttpResolver๋Š” JavaScript ๋กœ ์ž‘์„ฑ์‹œ๋Š” ๋ถˆํ•„์š”ํ•œ ์ฝ”๋“œ๋‹ค.

2. JSON Response

  • src/mocks/handlers/options.ts
import { http, HttpResponse } from "msw";
import { HttpResolver } from "./index";
import options from "../db/options";

const getOptionsResolver: HttpResolver = () => {
  return HttpResponse.json(options);
};

export const handlers = [http.get("/options", getOptionsResolver)];

๊ธฐ๋ณธ์ ์œผ๋กœ ์œ„์™€ ๊ฐ™์€ ํ˜•ํƒœ๋กœ JSON ํ†ต์‹  ์‘๋‹ต์„ ๋ณด๋‚ผ ์ˆ˜ ์žˆ๋‹ค.

3. Request Data & Delay

  • src/mocks/handlers/order.ts
import { HttpResolver } from "./index";
import { delay, http, HttpResponse } from "msw";
import { orderHistory, OrderInfo } from "../db/order";

const generateOrderNumber = () => Math.floor(Math.random() * 1000000);

const postOrderResolver: HttpResolver = async ({ request }) => {
  const price = await request.json().then((totals) => totals.price);
  const orderNumber = generateOrderNumber();
  const newOrder: OrderInfo = { orderNumber, price };
  orderHistory.push(newOrder);
  await delay(2000);
  return HttpResponse.json(orderHistory, { status: 201 });
};

export const handlers = [http.post("/order", postOrderResolver)];

src/mocks/handlers/index.ts ์—์„œ ์ž‘์„ฑํ•œ HttpResolver๋ฅผ ์‚ฌ์šฉํ•ด API ์š”์ฒญ์‹œ ์‹ค์–ด ๋ณด๋‚ด๋Š” ๋ฐ์ดํ„ฐ(Query Params, Body-data, Form-data ๋“ฑ)์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค. ๋˜ํ•œ ๊ธฐ๋ณธ์ ์œผ๋กœ delay ํ•จ์ˆ˜๋ฅผ ์ œ๊ณตํ•˜๊ธฐ ๋•Œ๋ฌธ์— ๋”ฐ๋กœ ์ •์˜ํ•  ํ•„์š” ์—†์ด ๊ฐ€์ ธ๋‹ค ์‚ฌ์šฉํ•˜๋ฉด ๋œ๋‹ค.

4. RESTful & Dynamic API

  • src/mocks/handlers/products.ts
import { HttpResolver } from "./index";
import { http, HttpResponse } from "msw";
import products from "../db/products";

const getProductsResolver: HttpResolver = () => {
  return HttpResponse.json(products);
};

const postProductResolver: HttpResolver = async ({ request }) => {
  const newPost = await request.json();
  // code...
  return HttpResponse.json({ id: "abc-123" }, { status: 201 });
};

const putProductResolver: HttpResolver = async ({ request, params }) => {
  const { id } = params;
  const updatePost = await request.json();
  // code...
  if (id === "abc-123") {
    return HttpResponse.json("success", { status: 204 });
  } else {
    return HttpResponse.error();
  }
};

const deleteProductResolver: HttpResolver = ({ params }) => {
  const { id } = params;
  if (id === "abc-123") {
    return HttpResponse.json("success", { status: 200 });
  } else {
    return HttpResponse.error();
  }
};

export const handlers = [
  http.get("/products", getProductsResolver),
  http.put("/products/:id", postProductResolver),
  http.delete("/products", deleteProductResolver),
];

์œ„์™€ ๊ฐ™์ด ๋ฉ”์„œ๋“œ๋งˆ๋‹ค ํ•ธ๋“ค๋Ÿด๋ฅด ์ƒ์„ฑํ•  ์ˆ˜ ์žˆ๋‹ค. ๋”ฐ๋ผ์„œ ์‹ค์ œ ์„œ๋ฒ„ API ๋ฅผ ์ƒ์„ฑํ•  ๋•Œ์™€ ๋งˆ์ฐฌ๊ฐ€์ง€๋กœ RESTful ๋ฐฉ์‹์„ ๊ทธ๋Œ€๋กœ Mocking ํ•  ์ˆ˜ ์žˆ๋‹ค. ๋˜ํ•œ /products/:id์™€ ๊ฐ™์ด URL Params ๋ฅผ ๊ตฌ๋ถ„ํ•  ์ˆ˜ ์žˆ์–ด Dynamic URL API ๋„ ์ •์˜๊ฐ€ ๊ฐ€๋Šฅํ•˜๋‹ค.

5. Static

์ด๊ฒƒ์€ ๊ณต์‹ ๋ฌธ์„œ์— ๋‚˜์˜ค๋Š” ๋ฐฉ๋ฒ•์ด๋‹ค.

import { http, HttpResponse } from 'msw'
 
export const handlers = [
  http.get('/images/:imageId', async ({ params }) => {
    const buffer = await fetch(`/static/images/${params.imageId}`).then(
      (response) => response.arrayBuffer()
    )

    return HttpResponse.arrayBuffer(buffer, {
      headers: {
        'Content-Type': 'image/jpeg',
      },
    })
  }),
]

์—ฌ๊ธฐ์„œ fetch๊ฐ€ ์š”์ฒญํ•˜๋Š” /static/images/...์˜ ๊ฒฝ๋กœ๋ฅผ ์ƒ๊ฐํ•ด๋ณด์ž. Browser Integration ์„ ํ–ˆ๊ธฐ ๋•Œ๋ฌธ์— Service Workers ๊ฐ€ fetch ์š”์ฒญ์„ ๋ณด๋‚ด๋Š” ๊ฒƒ์ด๋ฏ€๋กœ ํŒŒ์ผ์„ ์ฐพ๋Š” ์œ„์น˜๋Š” public/static/images/...๊ฐ€ ๋œ๋‹ค.

๋ฌธ์ œ๋Š” public์€ ํ”„๋ก ํŠธ์—”๋“œ ์•ฑ์ด ์ž์ฑ„์ ์œผ๋กœ ๊ณต๊ฐœ ์ œ๊ณตํ•˜๋Š” ํŒŒ์ผ๋“ค์„ ๋ชจ์•„๋‘๋Š” ํŠน๋ณ„ํ•œ ๋””๋ ‰ํ† ๋ฆฌ๋‹ค. ์ฆ‰, ๊ตณ์ด Mocking ์„ ํ•  ํ•„์š”๊ฐ€ ์—†์„ ๋ฟ ์•„๋‹ˆ๋ผ ์—ฌ๊ธฐ์— Mocking ์„ ์œ„ํ•œ ํŒŒ์ผ์„ ์„ž์–ด๋‘”๋‹ค๋ฉด ํ•ด๋‹น ํŒŒ์ผ๋“ค์ด ํ•จ๊ป˜ ๋นŒ๋“œ๋˜๋Š” ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•œ๋‹ค. ์šฐ๋ฆฌ๊ฐ€ ์›ํ•˜๋Š” ๊ฒƒ์€ ๋ฐฑ์—”๋“œ ์„œ๋ฒ„์˜ ์—ญํ• ์„ ๋Œ€์‹ ํ•  Mock Server ๋ฅผ ๋งŒ๋“œ๋Š” ๊ฒƒ์ด๋‹ค. ์ด ๋ฌธ์ œ๋ฅผ ์–ด๋–ป๊ฒŒ ํ™ฐ๊ฒฐํ•  ์ˆ˜ ์žˆ์„๊นŒ?


1 ) Make specific dir to only for MSW

์ฒซ ๋ฒˆ์งธ ๋ฐฉ๋ฒ•์€ ๊ณต์‹ ๋ฌธ์„œ์˜ ๋ฐฉ์‹์„ ๊ทธ๋Œ€๋กœ ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•ด public ํ•˜์œ„ ํŠน์ • ๋””๋ ‰ํ† ๋ฆฌ๋ฅผ MSW ์ „์šฉ์œผ๋กœ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด public/mocks/*๋Š” ์ „๋ถ€ Mocking ์„ ์œ„ํ•œ ํŒŒ์ผ์„ ๋ชจ์•„๋‘๋Š” ๊ฒฝ๋กœ๋กœ ์‚ฌ์šฉํ•˜๊ณ , ์ด ๋””๋ ‰ํ† ๋ฆฌ๋Š” ๋นŒ๋“œ ์˜ต์…˜์—์„œ ์ œ์™ธ์‹œํ‚จ๋‹ค. ๊ทธ๋Ÿฌ๋ฉด ์ •์ƒ์ ์ธ API Mocking ์ด ๊ฐ€๋Šฅํ•˜๋‹ค. ํ•˜์ง€๋งŒ public์€ ํ”„๋ก ํŠธ์—”๋“œ ์•ฑ์ด ์ž์‹ ์˜ ์„œ๋ฒ„์—์„œ ์ œ๊ณตํ•˜๊ณ ์ž ํ•˜๋Š” ๊ณต๊ฐœ ์ ‘๊ทผ์ด ํ—ˆ์šฉ๋œ ํŒŒ์ผ์„ ์œ„์น˜์‹œํ‚ค๋Š” ๋””๋ ‰ํ† ๋ฆฌ์ด๊ธฐ ๋•Œ๋ฌธ์— ์ž์นซ ์ž˜๋ชปํ•˜๋ฉด ํ˜ผ๋ž€์„ ์•ผ๊ธฐ์‹œํ‚ฌ ์ˆ˜ ์žˆ๋‹ค.


2 ) Include static files into src/mocks

๋‘ ๋ฒˆ์งธ ๋ฐฉ๋ฒ•์€ ๋‹ค๋ฅธ ๋ชจ๋“  ์ฝ”๋“œ์™€ ๋งˆ์ฐฌ๊ฐ€์ง€๋กœ src/mocks ํ•˜์œ„์— ํŒŒ์ผ์„ ์œ„์น˜์‹œํ‚ค๊ณ  ์ƒ๋Œ€ ๊ฒฝ๋กœ๋กœ ์ ‘๊ทผํ•ด ํŒŒ์ผ์„ ์ œ๊ณตํ•  ์ˆ˜ ์žˆ๋„๋ก ์ฝ”๋“œ๋ฅผ ๊ตฌํ˜„ํ•˜๋Š” ๊ฒƒ์ด๋‹ค. ์—ฌ๊ธฐ์„œ ์ค‘์š”ํ•œ ๊ฒƒ์€ ์ผ๋ฐ˜์ ์œผ๋กœ webpack ์˜ ์˜ต์…˜ ์ค‘ ๋ฆฌ์†Œ์Šค URL ์„ ๋‚œ๋…ํ™” ์‹œ์ผœ์ฃผ๋Š” ๊ธฐ๋Šฅ์„ ์‚ฌ์šฉํ•˜๊ธฐ ๋•Œ๋ฌธ์— ๋‹จ์ˆœํžˆ fetch์˜ ์š”์ฒญ URL ์„ ์ƒ๋Œ€ ๊ฒฝ๋กœ๋กœ ์ž‘์„ฑํ•˜๋Š” ๊ฒƒ์€ ์•„๋ฌด๋Ÿฐ ์˜๋ฏธ๊ฐ€ ์—†๋‹ค.

MSW Tree

import appleImage from "../static/images/apple.png"

์™€ ๊ฐ™์ด ์ฝ”๋“œ๋กœ ๋Œ€์ƒ ํŒŒ์ผ์„ import ์‹œํ‚ค๊ณ  ์ด๊ฒƒ์„ ์ด์šฉํ•ด fetch(appleImage)์™€ ๊ฐ™์ด ์ œ๊ณตํ•ด์•ผ ํ•œ๋‹ค๋Š” ๊ฒƒ์ด๋‹ค. ๋ฌธ์ œ๋Š” ์ด๋ฏธ์ง€ ํ•˜๋‚˜๋‘˜์ด์•ผ ์ด๋Ÿฐ์‹์œผ๋กœ ํ•  ์ˆ˜ ์žˆ์ง€๋งŒ ์ด๋ฏธ์ง€๊ฐ€ ๋งŽ์„ ๊ฒฝ์šฐ ์ผ์ผํžˆ ์ž‘์„ฑํ•ด์•ผ ํ•  ๋ฟ ์•„๋‹ˆ๋ผ, ์ด๋ฏธ์ง€๊ฐ€ ์ถ”๊ฐ€๋  ๋•Œ๋งˆ๋‹ค import ์ฝ”๋“œ ์—ญ์‹œ ๊ณ„์† ์ถ”๊ฐ€ํ•ด์•ผํ•œ๋‹ค.

์–ด๋–ป๊ฒŒ ํ•˜๋ฉด ์ด๊ฒƒ์„ dynamic ํ•˜๊ฒŒ ๋งŒ๋“ค ์ˆ˜ ์žˆ์„๊นŒ?

const response = await fetch(require(`../static/images/${imageId}`));
// ๋˜๋Š”
const response = await fetch((await import(`../static/images/${imageId}`)).default);

์™€ ๊ฐ™์ด ์š”์ฒญํ•˜๋Š” ๊ฒƒ์ด๋‹ค. ๋‹จ, ์ด๋•Œ ์ฃผ์˜ํ•ด์•ผ ํ•  ๊ฒƒ์ด

// ์ž˜๋ชป๋œ URL
`../static/images/${imageId}`

// ์˜ฌ๋ฐ”๋ฅธ URL
require(`../static/images/${imageId}`)
// ์˜ฌ๋ฐ”๋ฅธ URL
(await import(`../static/images/${imageId}`)).default

๋ผ๋Š” ๊ฒƒ์ด๋‹ค. ์ด ๊ฒฝ์šฐ ์•„๋ฌด๋ž˜๋„ await import ๋ณด๋‹ค๋Š” require๊ฐ€ ๊ฐ€๋…์„ฑ์ด ์ข‹๊ธฐ ๋•Œ๋ฌธ์— require๋ฅผ ์‚ฌ์šฉํ•˜๋„๋ก ํ•˜๊ฒ ๋‹ค.

  • src/mocks/handlers/static.ts
import { http, HttpResponse } from "msw";
import { HttpResolver } from "./index";

const getImageResolver: HttpResolver = async ({ params }) => {
  const { imageId } = params;

  const response = await fetch(require(`../static/images/${imageId}`));
  const buffer = await response.arrayBuffer();
  const contentType = response.headers.get("Content-Type");

  return HttpResponse.arrayBuffer(buffer, {
    headers: {
      "Content-Type": contentType || "image/jpeg",
    },
  });
};

export const handlers = [http.get("/images/:imageId", getImageResolver)];

์ด๋Ÿฐ์‹์œผ๋กœ images, videos, fonts ์™€ ๊ฐ™์€ ๋””๋ ‰ํ† ๋ฆฌ๋ฅผ ์ถ”๊ฐ€ํ•˜๊ณ  API ๋ฅผ ๋งŒ๋“ค๋ฉด ๋œ๋‹ค. ์ฐธ๊ณ ๋กœ ํ—ค๋”๋Š” Content-Type๋งŒ ์„ค์ •ํ•˜๋„๋ก ํ•œ๋‹ค. Content-Length ์™€ ๊ฐ™์€ ๋‹ค๋ฅธ ํ•ญ๋ชฉ๋“ค์€ arrayBuffer๋ฉ”์†Œ๋“œ๊ฐ€ ์ž๋™์œผ๋กœ ์ฑ„์›Œ ๋„ฃ์–ด์ค€๋‹ค.




Reference

  1. โ€œGetting Started syntax.โ€ mswjs Docs. accessed Jun. 01, 2024, https://mswjs.io.