아직도 한땀한땀 MockData 만든다고!? 이제 「Mock Service Worker」 쓰자 🤡

9rganizedChaos·2021년 11월 15일
17
post-thumbnail

What is Mock Service Worker !?

테스팅 서버를 직접 만들지 않고도 서버를 모킹할 수 있는 가장 좋은 방법, msw!

통상적으로 data fetch를 해야하는 경우 통신을 통해 응답을 내려주는 서버가 있어야 한다. 완전하게 서버가 구축되지 않은 단계에서, 프론트엔드 개발자들은 API 호출과 관련된 테스팅을 진행하기 위해 API를 모킹해주는 과정을 거친다. 그리고 이 과정은 여간 번거로운 작업이 아니다. 물론 따로 모킹 서버를 만들어줄 수도 있지만, 이는 더욱 번거로운 작업이다. 이러한 개발자의 고충을 msw가 해결해준다.

msw는 어떻게 API 모킹을 돕는 것일까!

Mock Service Worker라는 이름에서 알 수 있듯이 mswService Worker를 활용한다. Service Workder API란 놀랍게도, 모든 모던 브라우저에서 제공하는 표준 API이다! MDN 공식문서를 살펴보면 서비스워커에 대한 자세한 개념을 살필 수 있다.

msw를 학습하는데 있어 중요한 포인트만 짚어보자면, Service Worker는 일종의 대리 서버라고 할 수 있다. Service Worker를 활용하면, 리소스 요청을 가로채 수정할 수 있고, 리소스를 세부적으로 캐싱해 조작할 수 있다. 즉, 네트워크를 사용하지 못할 때에도 개발자가 개발하고 있는 웹앱이 어떻게 동작해야 하는지 조작할 수 있다는 것이다!

msw의 컨셉 역시 Service Workder API와 크게 다르지 않다!
msw 공식페이지에서 제공하는 아래의 이미지를 살펴보자.

브라우저가 보낸 request를 Service Worker가 가로챈 후 msw를 통해서 모킹된 response를 돌려주고 있는 모습을 확인할 수 있다.

Mock Service Worker is an API mocking library that uses Service Worker API to intercept actual requests.

이제 위와 같은 공식문서의 msw에 대한 설명을 문제없이 이해할 수 있을 것이다.
즉, msw는 유저의 request를 서버까지 보내지 않고 네트워크레벨에서 인터셉트해 임의의 데이터를 응답해주는 라이브러리라고 요약해볼 수 있겠다!

msw를 사용해보자!

그렇다면 msw를 사용해보자! 공식문서를 살펴보면 msw는 react와 graphql모두 대응한다는 것을 알 수 있다. 또, Rest API/GraphQL까지 모두 모킹이 가능하다!

오늘 이 포스팅에서는 React를 통해 msw를 활용해보도록 한다!

페북 가라사대 태초에 npx create-ract-app 이 있었나니

$ npx create-react-app msw-practice

우선 위와 같은 식으로 react 프로젝트를 하나 시작해준다. (물론 프로젝트 이름은 재량껏 지으면 된다.)

msw를 인스톨해주자!

$ npm install msw --save-dev
# or
$ yarn add msw --dev

이렇게 인스톨을 해주고 나서 package.json을 확인하면 개발 의존 모듈에서 msw를 확인해볼 수 있다.

mocks 폴더를 생성해주자!

src 폴더 안에 mocks 폴더를 생성해주자.
mocks 폴더 내부에 필요한 파일은 두 가지이다! (만일 node.js 실행환경에서 실행할 것이라면 한 가지가 더 필요하나 이 포스팅에서는 생략, 해당 부분은 공식홈페이지 메인 화면에서 소개되는 example 코드를 통해 확인할 수 있다.)

mocks 폴더 내부에 handlers.js를 생성해준다.

handlers.js는 마치 서버에서 컨트롤러를 작성해주는 것과 비슷하게 생각해도 좋다.
브라우저에서 날아온 request를 인터셉트했을 때 어떤 엔드포인트와 요청에 따라 어떤 결과를 돌려줄지 미리 작성해주는 과정이다.

// graphql을 이용하려면, graphql을 꺼내오면 된다.
// 현재 여기서는 restAPI로 실습하기 위해 rest를 꺼내왔다.
import { rest } from "msw";

export const handlers = [
  rest.get("https://pokeapi.co/api/v2/pokemon/", async (req, res, ctx) => {
    return res(
      ctx.json({
        count: 1118,
        next: "https://pokeapi.co/api/v2/pokemon/?offset=20&limit=20",
        previous: null,
        results: [
          {
            name: "이상해씨",
            url: "https://pokeapi.co/api/v2/pokemon/1/",
          },
          {
            name: "이상해풀",
            url: "https://pokeapi.co/api/v2/pokemon/2/",
          },
          {
            name: "이상해꽃",
            url: "https://pokeapi.co/api/v2/pokemon/3/",
          },
          {
            name: "파이리",
            url: "https://pokeapi.co/api/v2/pokemon/4/",
          },
          {
            name: "리자드",
            url: "https://pokeapi.co/api/v2/pokemon/5/",
          },
          {
            name: "리자몽",
            url: "https://pokeapi.co/api/v2/pokemon/6/",
          },
          {
            name: "꼬부기",
            url: "https://pokeapi.co/api/v2/pokemon/7/",
          },
          {
            name: "어니부기",
            url: "https://pokeapi.co/api/v2/pokemon/8/",
          },
          {
            name: "거북왕",
            url: "https://pokeapi.co/api/v2/pokemon/9/",
          },
        ],
      })
    );
  }),
];

간단하게 포켓몬에 대한 정보를 불러오는 연습을 해보기 위해 위와 같이 작성했다.

그리고 나서, browser.js 파일을 작성해준다.

browser.js에서는 msw에서 제공하는 서비스워커를 가져와서 위에서 작성한 handlers를 인자로 넘겨준다.

import { setupWorker } from "msw";
import { handlers } from "./handlers";

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

여기서 끝이 아니다! msw 초기화를 진행해주자!

이렇게 파일만 작성해준다고 바로 msw를 사용할 수 있는 것은 아니다.
공식문서를 차근차근 살펴보면, 아래와 같은 명령어로 cli에서 init을 해줘야 한다는 것을 알 수 있다.

$ npx msw init <PUBLIC_DIR> --save

PUBLIC_DIR은 어떤 프레임워크 혹은 라이브러리를 사용하고 있느냐에 따라 다른데, 우리는 리액트를 사용하고 있으므로 public/을 입력해주면 된다!

이제 API를 요청해보기 위해서 컴포넌트를 작성해주자!

아래와 같이 components 폴더 내부에 PokemonInfo라는 컴포넌트를 만들어준다!

import React, { useEffect, useState } from 'react';
import axios from 'axios';

const PokemonCard = ({name, url}) => {

  const [photoUrl, setPhotoUrl] = useState("");

  useEffect(() => {
    axios.get(url)
    .then(res => {
      setPhotoUrl(res.data.sprites.front_default);
    })
    .catch(error => {
      console.log(error)
    })
  })

  return (
    <div>
      <img src={photoUrl} alt={`${name}-front-default`} />
      <h3>{name}</h3>
    </div>
  )
}

const mainUrl = "https://pokeapi.co/api/v2/pokemon/"

export default function PokemonList() {
  const [data, setData] = useState(null);
  const [error, setError] = useState(null);

  const handleClick = () => {
    axios.get(mainUrl)
      .then(response => {
        setData(response.data);
      })
      .catch(error => {
        setError(`Something Wrong: ${error}`);
      })
  }

  if(error) {
    return <p>{error}</p>
  }

  return (
    <div>
      <button onClick={handleClick}>포켓몬 정보 조회하기</button>
      {data && (
        <div>
          {data.results.map((pokemon) => {
            return <PokemonCard 
              key={`${pokemon.name}-${pokemon.url}`} 
              name={pokemon.name} 
              url={pokemon.url}/>
          })}
        </div>
      )}
    </div>
  )
}

그럼 이제, 프로젝트를 실행시켜보자!

어딘가 이상하다! 분명 handlers.js에서 우리는 한글로 포켓몬들의 이름을 작성해주었었다.
게다가 네트워크 탭을 확인해보아도 pokemon에 대한 response가 서비스 워커로부터 오고 있지 않다는 것을 알 수 있다. 현재 response는 PokeAPI에서 오고 있는 것이다! 서비스워커가 요청을 인터셉트하지 않았다는 것이다.

index.js로 이동!

우리가 놓친 마지막 스텝은 바로, index.js에서 서비스워커를 실행시켜주는 것이다!

//Start the mocking conditionally/
if (process.env.NODE_ENV === "development") {
  const { worker } = require("./mocks/browser");
  worker.start();
}

index.js에서 render 윗 부분에 위와 같은 코드를 추가해준다!
다시 한 번 프로젝트를 실행하고, API 요청을 보내면!

이렇게나 앙증맞고 귀여운 포켓몬들을 확인할 수가 있다!
이에 더해 네트워크 탭을 열어보면 상태코드 옆에서 from service worker라는 안내 문구를 확인할 수 있다.

이 얼마나 놀라운 일인가!

프론트엔드 지망 부트캠프 수강생들이여!
이제 프로젝트 기획이 끝나고 백엔드 동료들이 API 구축해주길 하염없이 기다리지 말고,
localhost로 url 적어주었다가 다시 다~ 나중에 수정해주지 말고!
테스트 코드에 목업 데이터 일일이 만들어주지 말고!

msw를 활용하자!

profile
부정확한 정보나 잘못된 정보는 댓글로 알려주시면 빠르게 수정토록 하겠습니다, 감사합니다!

0개의 댓글