Front단에서 API 만들기 - Mock Service Worker (Version 2)

boyeonJ·2023년 12월 11일
4

FRONT

목록 보기
14/14

Mock Service Worker

mock service worker는 Service Worker를 사용하여 네트워크 호출을 가로채는 API 모킹(mocking) 라이브러리입니다. 한마디로 브라우저를 속여 마치 백엔드 API인척 가짜 데이터를 제공해줄 수 있는 라이브러리입니다.

이미지 출처

Why

1. 빠른 개발

프론트엔드 개발은 프로세스 순서의 가장 마지막에 위치해 있습니다. 그로인하여 실제 개발 프로젝트에서 기획, 백엔드 개발이 완료될때까지 대기하다가 개발이 뒤로 밀려 완성도가 낮아지는 경우가 존재합니다.

그런데 만약 프론트엔드 개발자가 미리 api를 개발하고 테스트 할 수 있는 환경이 제공된다면 어떨까요?
만약 백엔드 개발과 프론트 개발이 병렬적으로 협업하게 되어 의존성을 떨어트리게 된다면 빠른 개발이 가능한 환경이 제공될 것입니다. 이는 개발의 완성도를 높일 수 있는 발판이 될 것입니다.

2. 테스트 신뢰도

테스트를 할때 발생하는 외부 요인(네트워크, 서버)들이 존재합니다. 이는 테스트의 일관성과 신뢰도를 떨어트릴수 있습니다.

그에 반해 msw는 외부 요인의 영향을 받지 않기 때문에 일관된 테스트를 진행할 수 있습니다.

How

1. Install

npm install msw --save-dev

2. 요청 가로채기

request에 따른 mock response를 보내기 위해서는 먼저 요청을 intercept 해야 합니다. 여기서 필요한 것은 predicate, resolver 함수 입니다. 이 두가지를 msw에서 제공하는 http 함수에 알맞게 보내면 됩니다.

2-1. 하나씩 보내기

import { http, HttpResponse } from 'msw'
 
http.get(
  '/pets',
  ({ request, params, cookies }) => {
    return HttpResponse.json(['Tom', 'Jerry', 'Spike'])
  }
)

2-2. 한번에 여러개 보내기

export const handlers = [
  http.get('/pets', petsResolver),
  http.post('https://api.github.com/repo', repoResolver),
]

path 관련된 정규식 사용 가능합니다.

// Intercept all GET requests to "/user":
// - GET /user
// - GET /user/abc-123
// - GET /user/abc-123/settings
http.get('/user/*', userResolver)

3. 응답 보내기

3-1. staus code + text

import { http, HttpResponse } from 'msw'
 
export const handlers = [
  http.get('/apples', () => {
    return new HttpResponse(null, {
      status: 404,
      statusText: 'Out Of Apples',
    })
  }),
]

3-2. Mocking headers

import { http, HttpResponse } from 'msw'
 
export const handlers = [
  http.post('/auth', () => {
    return new HttpResponse(null, {
      headers: {
        'Set-Cookie': 'mySecret=abc-123',
        'X-Custom-Header': 'yes',
      },
    })
  }),
]

3-3. text

import { http, HttpResponse } from 'msw'
 
export const handler = [
  http.get('/name', () => {
    return new HttpResponse('John')
  }),
]

3-4. json

import { http, HttpResponse } from 'msw'
 
export const handlers = [
  http.post('/auth', () => {
    // Note that you DON'T have to stringify the JSON!
    return HttpResponse.json({
      user: {
        id: 'abc-123',
        name: 'John Maverick',
      },
    })
  }),
]

실제 적용

1. 설치와 init

msw를 설치한 후 브라우저 service worker를 등록해주기 위해서는 init이 필요합니다.

npm install msw --save-dev
npx msw init public/ --save

init 명령어를 실행하면 public 폴더 하위에 mockServiceWorker.js 파일이 자동 생성됩니다.

2. 필요한 파일 만들기

msw는 hanlders를 만들고 browser에 setup 해주고 setup한 server를 start 해주는 과정이 필요합니다. 이를 위해서 server(mocks)폴더에 browser, handlers 파일을 만들어줍니다.

3. handlers 작성

아주 간단하게 products를 가져오는 handler를 생성하고 handlers에 포함시켜줍니다.(모듈화 안한 코드)

import { http, HttpResponse } from "msw";

interface Product {
  id: number;
  brand: string;
  name: string;
  price: number;
  rate: number;
  review: number;
}

const products: Product[] = [
  {
    id: 1,
    brand: "LG전자",
    name: "LG스타일러 5벌+1벌 S5MBAUE 블랙미러+실내제습",
    price: 1235440,
    rate: 19,
    review: 344,
  },
  {
    id: 2,
    brand: "아엠홈",
    name: "비침없는 도톰 레이스/쉬폰커튼(나비주름/핀형/봉집)",
    price: 41600,
    rate: 40,
    review: 31258,
  },
  {
    id: 3,
    brand: "동원",
    name: "동원참치 85g*12캔 3종 (라이트스탠다드/고추/콘참치)",
    price: 17980,
    rate: 25,
    review: 4475,
  },
  {
    id: 4,
    brand: "LG전자",
    name: "LG 디오스 식기세척기 오브제컬렉션 DUBJ2EAL",
    price: 929900,
    rate: 26,
    review: 250,
  },
  {
    id: 5,
    brand: "LG전자",
    name: "비스포크 WF24B9600KE+DV20B9760CE 오토오픈도어",
    price: 2242719,
    rate: 7,
    review: 22,
  },
];

const productsResolver = () => {
  return HttpResponse.json({ products });
};

export const handlers = [http.get("/products", productsResolver)];

4. borwers에 setUp 해주기

위에서 작성한 handlers를 setup 해줍니다.

import { setupWorker } from 'msw/browser'
import { handlers } from './handlers';

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

Service Worker는 브라우저에만 사용할 수 있습니다. 따라서 jest와 같이 node에서 사용하려면 따로 server setupServer함수를 작성하여 setup해주어야 합니다.

5. start해주기

main.tsx에서 setUp한 Worker를 불러온 후 start 해줍니다.

import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App.tsx";
import "./index.css";

const root = ReactDOM.createRoot(document.getElementById("root")!);

const mock = async () => {
  const { server } = await import("./server/browser");
  server.start();
};

mock().then(() => {
  root.render(
    <React.StrictMode>
      <App />
    </React.StrictMode>
  );
});

정상적으로 mockServer가 실행되었다면 크롬 개발자도구에 Mocking enabled. 라는 글자를 확인 할 수 있습니다.

6. axios를 통해 mock api 호출하기


import Typography from "../atoms/Typography";
import colors from "../atoms/Color";
import { useEffect, useState } from "react";
import axios from "axios";

interface Product {
  id: number;
  brand: string;
  name: string;
  price: number;
  rate: number;
  review: number;
}

const Products = () => {
  const [products, setProducts] = useState<Product[]>([]);

  const fetchPosts = async () => {
    const res = await axios({
      method: "get",
      url: "/products",
    });

    setProducts(res.data.products);
    console.log(res);
  };

  useEffect(() => {
    fetchPosts();
  }, []);

  return (
    <section
      css={{
        border: `1px solid ${colors["black"]}`,
        display: "grid",
        gridTemplateColumns: "repeat(4, 23%)",
        justifyContent: "space-between",
      }}
    >
      {products.map((product: Product) => (
        <div
          key={product.id}
          css={{
            display: "flex",
            flexDirection: "column",
            alignItems: "start",
          }}
        >
          <Typography variant="h4" color="gray1">
            {product.brand}
          </Typography>
          <Typography variant="h2">{product.name}</Typography>
          <div>
            <Typography variant="h1B" color="primary">
              {product.rate}
            </Typography>
            <Typography variant="h1B">{product.price}</Typography>
          </div>
          <Typography variant="h4B" color="gray2">
            {`리뷰 ${product.review}`}
          </Typography>
        </div>
      ))}
    </section>
  );
};

export default Products;

0개의 댓글