Tech Blog of Pinomaker
article thumbnail
Published 2023. 9. 23. 11:11
MSW로 Mocking(1) - Brower F.E/React

이번 글은 MSW를 이용하여 백엔드 API를 Mocking 하는 방법에 대해 알아보자. 실제로 프로젝트에 들어가게된다면 내가 프론트 엔드 작업을 할 때 API가 있을 수도 있지만, 없는 경우도 많다. 보통 프로젝트를 할 때는 프론트엔드에서 UI를 구성하는 작업과 백엔드의 API를 개발하는 작업이 작업 순서상 동시에 진행되는 데 이러면 UI를 구성할 때 백엔드로부터 데이터를 받아올 수 없기 때문에 Fake 데이터인 더미 데이터를 만들어서 사용한다. 

 

그렇게 만들어둔 더미 데이터는 API가 완성된다면 사용하지 않고 백엔드로부터 데이터를 받아오지만 단순하게 가짜 데이터인 더미 데이터를 사용하는 것이 아니라 실제로 호출할 수 있는 가짜 API를 사용하면 좀 더 안정적으로 UI를 구성할 수 있다. 

 

Mock Service Worker

MSW는 Service Worker를 사용하여 네트워크 호출을 가로채는 API 모킹 라이브러리로, 쉽게 말해서 네트워크 호출을 가로챌 수 있는 가짜 API를 만들 수 있는 API 모킹 라이브러다.

 

비교적 최근에 웹 표준 기술인 서비스워커를 사용하면 브라우저로부터 나가고 들어오는 요청, 응답을 중간에서 감시하거나 변조 캐싱과 같은 부가적인 작업을 할 수 있다. 

 

기존 모킹 라이브러리와 비교하여 MSW의 장점은 모킹이 실제 네트워크 단에서 발생하기 때문에 모킹하는 프론트엔드 코드를 실제 백엔드 API와 통신하는 것과 다르지 않게 작성할 수 있고, 추후에 실제 백엔드 API로 간단하게 교체할 수 있다.

 

또한 브라우저에 뿐만 아니라 Node.js 환경에서 Jest와 같은 테스트 러너로 돌려서 사용할 수 있는 데, 이는 API 모킹을 위해 작성해야하는 코드를 최소화하여 리소스를 줄일 수 있는 장점이 있다. 또한 REST API와 GraphQL도 지원한다.

 

 

MSW를 이용하여 간단하게 Mocking 하기.

먼저 msw를 개발용으로 yarn을 이용하여 추가하고, 기반 코드를 자동 생성시키자.

# msw 설치
yarn add -D msw

# 기반 코드 생성
npx msw init public/ --save


MSW는 브라우저에서 서비스 워커를 통해서 작동하기에 서비스 워커를 등록하는 기본적인 코드가 필요하기에 MSW에서 제공하는 CLI를 이용하여 쉽게 만드는 데, Public/ 부분에는 프로젝트에서 정적 리소스를 두는 폴더를 지정하면 된다.

 

그 후에는 가짜 API를 구현하기 위하여 응답을 해주는 핸들러 코드를 작성해야하며, 어디에 작성하든 상관은 없다고 하지만 보통은 mocks 디렉토리에 작성한다.

 

// src/mocks/handlers/todo/index.ts

// ** msw Imports
import { rest } from 'msw'
import { Todo } from './mock.todo'

// ** Fake Imports

const getTodo = (isError?: boolean) => {
  return rest.get('/todo', (_, res, ctx) => {
    if (isError) {
      return res(ctx.status(500))
    }

    return res(ctx.status(200), ctx.json(Todo))
  })
}

export const todoHandler = [getTodo()]

 

핸들러는 MSW에서 제공하는 rest를 이용하여 요청을 가로챌 수 있으며, 어떤 URL에 어떤 Method에 오는 API라고 선언을 하고 이에 대한 코드를 작성할 수 있다. 나같은 경우는 리턴할 더미 데이터는 아래와 같이 따로 분리해서 작성해두었다.

 

// src/mocks/handlers/todo/mock.todo.ts

export const Todo = [
  {
    id: 1,
    text: '배고프다.',
  },
  {
    id: 2,
    text: '배고프다.',
  },
  {
    id: 3,
    text: '배고프다.',
  },
  {
    id: 4,
    text: '배고프다.',
  },
]

 

handler는 도메인을 기준으로 나누어 작성하였고, 만든 핸들러들을 아래와 같이 하나의 핸들러로 만들어서 관리한다.

 

// src/mocks/handlers/index.ts

// ** Handler Imports
import { todoHandler } from './todo'

export const handlers = [...Object.values(todoHandler)]

 

위에서 MSW는 브라우저 환경 뿐만 아니라 Node.js와 같은 환경에서도 돌아간다고 말하였는 데, 이 때 아래와 같이 브라우저냐 혹은 서버냐에 따라서 워커를 만들 수 있다.

 

// src/mocks/index.ts

import { setupServer } from 'msw/node'
import { handlers } from './handlers'
import { setupWorker } from 'msw'

// nodejs용 
export const server = setupServer(...handlers)

// 브라우저용
export const worker = setupWorker(...handlers)

 

먼저 브라우저용으로 확인해보자. 나는 CRA가 아니라 VITE로 React를 생성하였기에, index.tsx가 아닌 main.tsx가 메인 파일이다. test나 prod 환경에서는 굳이 모킹 데이터를 이용할 필요가 없기에 development 환경에서만 MSW를 실행시키는 코드를 작성한다.

 

// main.tsx

// ** React Imports
import ReactDOM from 'react-dom/client'

// ** Router Imports
import { BrowserRouter } from 'react-router-dom'

// ** Components Imports
import App from './App'

// ** Style Imports
import '@/style/global.css'

import { worker } from './mocks'

if (process.env.NODE_ENV === 'development') {
  worker.start()
}

const root = ReactDOM.createRoot(document.getElementById('root') as HTMLElement)

root.render(
    <BrowserRouter>
      <App />
    </BrowserRouter>
)

 

자 이제 우리가 만든 MSW 가짜 API를 이용하여 간단한 리스트를 UI에 그리는 코드를 짜보자.

 

// ** React Imports
import { Fragment, useEffect, useState } from 'react'

// ** Type Imports
import type { Todo } from './types/todo'

const App = () => {
  const [todo, setTodo] = useState<Todo[]>([])

  useEffect(() => {
    fetch('/todo').then(async (response) => {
      const body = await response.json()
      setTodo(body)
    })
  }, [])

  return (
    <Fragment>
      <h1>MSW TEST</h1>
      {todo.map((item) => (
        <li key={item.id}>{item.text}</li>
      ))}
    </Fragment>
  )
}

export default App

 

 

해당 코드를 보면 fetch를 이용하여 백엔드에게 데이터를 요청하여 받아오는 코드인데, 실제 API가 없지만 MSW가 해당 API를 가로채 우리가 앞서 설정한 데이터를 응답으로 넘겨준다.

 

네트워크 탭을 보면 실제 백엔드는 없지만 우리가 핸들러에서 설정한 데이터가 실제 백엔드에서 내려주는 것과 같이 응답 되는 것을 확인할 수 있고 이에 아래와 같은 UI를 백엔드 없이 구현이 되는 것을 볼 수 있다.

profile

Tech Blog of Pinomaker

@pinomaker

포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!