MSW(Mock Service Worker) 완벽 가이드

odada·2025년 1월 12일

api

목록 보기
1/1

목차

MSW란?

MSW(Mock Service Worker)는 서비스 워커를 사용하여 네트워크 요청을 가로채고 모의 응답을 제공하는 API 목킹(mocking) 라이브러리입니다.

MSW의 장점

  1. 실제 네트워크 요청을 가로채서 처리하므로 실제 API처럼 동작
  2. 브라우저와 Node.js 환경 모두 지원
  3. REST API와 GraphQL 모두 지원
  4. TypeScript 지원
  5. 테스트 환경 구축이 쉬움

설치 방법

1. NPM으로 설치

# npm 사용
npm install msw --save-dev

# yarn 사용
yarn add -D msw

2. Service Worker 설정 파일 생성

npx msw init public/ --save
  • public/ 은 당신의 정적 파일이 제공되는 디렉토리입니다
  • Next.js를 사용하는 경우 public/
  • Create React App을 사용하는 경우 public/
  • Vite를 사용하는 경우 public/

기본 설정

1. handlers.js 파일 생성

// src/mocks/handlers.js
import { rest } from 'msw'

export const handlers = [
  // GET 요청 처리
  rest.get('/api/users', (req, res, ctx) => {
    return res(
      ctx.status(200),
      ctx.json([
        { id: 1, name: 'John Doe' },
        { id: 2, name: 'Jane Doe' },
      ])
    )
  }),

  // POST 요청 처리
  rest.post('/api/users', (req, res, ctx) => {
    return res(
      ctx.status(201),
      ctx.json({ message: 'User created' })
    )
  })
]

2. browser.js 설정 파일 생성

// src/mocks/browser.js
import { setupWorker } from 'msw'
import { handlers } from './handlers'

export const worker = setupWorker(...handlers)

3. 개발 환경에서 MSW 활성화

// src/index.js 또는 main.js
async function prepare() {
  if (process.env.NODE_ENV === 'development') {
    const { worker } = await import('./mocks/browser')
    worker.start()
  }
}

prepare()

핸들러 작성하기

기본적인 CRUD 작업 예시

// src/mocks/handlers.js
import { rest } from 'msw'

export const handlers = [
  // 조회 (GET)
  rest.get('/api/users', (req, res, ctx) => {
    return res(
      ctx.status(200),
      ctx.json([
        { id: 1, name: 'John' },
        { id: 2, name: 'Jane' }
      ])
    )
  }),

  // 단일 항목 조회 (GET)
  rest.get('/api/users/:id', (req, res, ctx) => {
    const { id } = req.params
    return res(
      ctx.status(200),
      ctx.json({
        id: parseInt(id),
        name: `User ${id}`
      })
    )
  }),

  // 생성 (POST)
  rest.post('/api/users', async (req, res, ctx) => {
    const body = await req.json()
    return res(
      ctx.status(201),
      ctx.json({
        id: Math.random(),
        ...body
      })
    )
  }),

  // 수정 (PUT)
  rest.put('/api/users/:id', async (req, res, ctx) => {
    const { id } = req.params
    const body = await req.json()
    return res(
      ctx.status(200),
      ctx.json({
        id: parseInt(id),
        ...body
      })
    )
  }),

  // 삭제 (DELETE)
  rest.delete('/api/users/:id', (req, res, ctx) => {
    const { id } = req.params
    return res(
      ctx.status(200),
      ctx.json({
        message: `User ${id} deleted`
      })
    )
  })
]

실제 사용 예시

React에서 사용하기

// UserList.js
import React, { useState, useEffect } from 'react'

function UserList() {
  const [users, setUsers] = useState([])
  const [loading, setLoading] = useState(false)
  const [error, setError] = useState(null)

  useEffect(() => {
    const fetchUsers = async () => {
      setLoading(true)
      try {
        const response = await fetch('/api/users')
        const data = await response.json()
        setUsers(data)
      } catch (err) {
        setError(err.message)
      } finally {
        setLoading(false)
      }
    }

    fetchUsers()
  }, [])

  if (loading) return <div>Loading...</div>
  if (error) return <div>Error: {error}</div>

  return (
    <ul>
      {users.map(user => (
        <li key={user.id}>{user.name}</li>
      ))}
    </ul>
  )
}

export default UserList

폼 제출 예시

// UserForm.js
import React, { useState } from 'react'

function UserForm() {
  const [name, setName] = useState('')
  const [message, setMessage] = useState('')

  const handleSubmit = async (e) => {
    e.preventDefault()
    try {
      const response = await fetch('/api/users', {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
        },
        body: JSON.stringify({ name })
      })
      const data = await response.json()
      setMessage('User created successfully!')
      setName('')
    } catch (err) {
      setMessage('Error creating user')
    }
  }

  return (
    <form onSubmit={handleSubmit}>
      <input
        type="text"
        value={name}
        onChange={(e) => setName(e.target.value)}
        placeholder="Enter user name"
      />
      <button type="submit">Create User</button>
      {message && <p>{message}</p>}
    </form>
  )
}

export default UserForm

고급 기능

1. 지연 응답 시뮬레이션

rest.get('/api/users', (req, res, ctx) => {
  return res(
    ctx.delay(1000), // 1초 지연
    ctx.status(200),
    ctx.json([/* ... */])
  )
})

2. 조건부 응답

rest.get('/api/users', (req, res, ctx) => {
  const authorized = req.headers.get('Authorization')
  
  if (!authorized) {
    return res(
      ctx.status(401),
      ctx.json({ message: 'Not authorized' })
    )
  }

  return res(
    ctx.status(200),
    ctx.json([/* ... */])
  )
})

3. 쿼리 파라미터 처리

rest.get('/api/users', (req, res, ctx) => {
  const searchQuery = req.url.searchParams.get('search')
  
  const users = [
    { id: 1, name: 'John' },
    { id: 2, name: 'Jane' }
  ]

  if (searchQuery) {
    const filteredUsers = users.filter(user => 
      user.name.toLowerCase().includes(searchQuery.toLowerCase())
    )
    return res(ctx.json(filteredUsers))
  }

  return res(ctx.json(users))
})

문제 해결

자주 발생하는 문제들

  1. Service Worker가 등록되지 않는 경우

    • public/ 디렉토리에 mockServiceWorker.js 파일이 있는지 확인
    • 올바른 경로로 init 명령어를 실행했는지 확인
  2. 핸들러가 동작하지 않는 경우

    • worker.start()가 제대로 호출되었는지 확인
    • 개발 환경(NODE_ENV === 'development')인지 확인
    • 콘솔에 에러 메시지가 있는지 확인
  3. 응답이 예상과 다른 경우

    • 핸들러의 경로가 정확한지 확인
    • HTTP 메서드가 올바른지 확인
    • 요청 헤더와 바디가 예상대로인지 확인

Best Practices

  1. 파일 구조 관리
src/
  mocks/
    handlers/
      auth.js
      users.js
      posts.js
    browser.js
    handlers.js
  1. 데이터 모킹
// src/mocks/data/users.js
export const users = [
  { id: 1, name: 'John', email: 'john@example.com' },
  { id: 2, name: 'Jane', email: 'jane@example.com' }
]

// src/mocks/handlers/users.js
import { users } from '../data/users'

export const userHandlers = [
  rest.get('/api/users', (req, res, ctx) => {
    return res(ctx.json(users))
  })
]
  1. 테스트에서 활용
// src/components/UserList.test.js
import { rest } from 'msw'
import { setupServer } from 'msw/node'
import { render, screen } from '@testing-library/react'
import UserList from './UserList'

const server = setupServer(
  rest.get('/api/users', (req, res, ctx) => {
    return res(ctx.json([
      { id: 1, name: 'Test User' }
    ]))
  })
)

beforeAll(() => server.listen())
afterEach(() => server.resetHandlers())
afterAll(() => server.close())

test('renders user list', async () => {
  render(<UserList />)
  expect(await screen.findByText('Test User')).toBeInTheDocument()
})

환경별 설정 관리

// src/mocks/config.js
export const API_DELAY = process.env.REACT_APP_API_DELAY || 1000
export const FAIL_RATE = process.env.REACT_APP_FAIL_RATE || 0

// src/mocks/handlers/users.js
import { API_DELAY, FAIL_RATE } from '../config'

export const userHandlers = [
  rest.get('/api/users', (req, res, ctx) => {
    // 일정 확률로 실패 시뮬레이션
    if (Math.random() < FAIL_RATE) {
      return res(
        ctx.status(500),
        ctx.json({ message: 'Internal Server Error' })
      )
    }

    return res(
      ctx.delay(API_DELAY),
      ctx.status(200),
      ctx.json(users)
    )
  })
]

이 가이드를 통해 MSW를 프로젝트에 도입하고 효과적으로 활용할 수 있습니다. 추가 질문이나 특정 사용 사례에 대한 도움이 필요하시다면 언제든 문의해주세요.

0개의 댓글