MSW(Mock Service Worker)는 서비스 워커를 사용하여 네트워크 요청을 가로채고 모의 응답을 제공하는 API 목킹(mocking) 라이브러리입니다.
# npm 사용
npm install msw --save-dev
# yarn 사용
yarn add -D msw
npx msw init public/ --save
// 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' })
)
})
]
// src/mocks/browser.js
import { setupWorker } from 'msw'
import { handlers } from './handlers'
export const worker = setupWorker(...handlers)
// src/index.js 또는 main.js
async function prepare() {
if (process.env.NODE_ENV === 'development') {
const { worker } = await import('./mocks/browser')
worker.start()
}
}
prepare()
// 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`
})
)
})
]
// 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
rest.get('/api/users', (req, res, ctx) => {
return res(
ctx.delay(1000), // 1초 지연
ctx.status(200),
ctx.json([/* ... */])
)
})
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([/* ... */])
)
})
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))
})
Service Worker가 등록되지 않는 경우
핸들러가 동작하지 않는 경우
응답이 예상과 다른 경우
src/
mocks/
handlers/
auth.js
users.js
posts.js
browser.js
handlers.js
// 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))
})
]
// 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를 프로젝트에 도입하고 효과적으로 활용할 수 있습니다. 추가 질문이나 특정 사용 사례에 대한 도움이 필요하시다면 언제든 문의해주세요.