MSW는 Mock Service Worker로 서비스 워커를 이용하여 API를 모킹하는 라이브러리입니다. 네트워크 요청을 가로채도록 설계된 Service Worker API를 활용하기 때문에 목 사용 여부 관계 없이 동일한 애플리케이션 동작을 보장합니다.
MSW를 여러가지 용도로 활용할 수 있지만 대표적으로 두가지 사례를 생각해볼 수 있습니다
첫번째는 백앤드 API 개발과 프런트앤드 UI 개발이 동시에 진행되야하는 경우, 백앤드 API 구현이 완료될 때까지 프런트앤드 팀에서 임시로 사용하기 위한 가짜(mock) API를 서비스 워커로 돌리기 위해서이고, 두번째는 테스트를 실행 시 실제 백앤드 API에 네트워크 호출을 하는 대신에 훨씬 빠르고 안정적인 가짜 API 서버를 구축하기 위해서 입니다.
//index.js
import React from 'react'
import ReactDOM from 'react-dom'
import { LoginForm } from './LoginForm'
// Start the mocking conditionally.
if (process.env.NODE_ENV === 'development') {
const { worker } = require('./mocks/browser')
worker.start()
}
ReactDOM.render(<LoginForm />, document.getElementById('root'))
//browser.js
import { setupWorker } from 'msw'
import { handlers } from './handlers'
export const worker = setupWorker(...handlers)
//handler.js
import { rest } from 'msw'
export const handlers = [
rest.post('/login', (req, res, ctx) => {
const { username } = req.body
return res(
ctx.json({
id: 'f79e82e8-c34a-4dc7-a49e-9fadc0979fda',
username,
firstName: 'John',
lastName: 'Maverick',
})
)
}),
]
//react app 설치
npx create-react-app fetch
//msw 설치
npm install msw --save-dev
//공용디렉토리에 CLI실행
npx msw init public --save
//app
import './App.css';
import TestMocking from './components/TestMocking';
function App() {
return (
<div className="App">
<TestMocking/>
</div>
);
}
export default App;
//index
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
---------
//추가
if (process.env.NODE_ENV === 'development') {
const { worker } = require('./mocks/browser')
worker.start()
}
---------
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
);
// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();
// component/TestMocking.jsx
import React, { useState } from 'react';
const Item = ({name, age}) => {
return (
<li>
name:{name} / age:{age}
</li>
)
}
const TestMocking = () => {
const [data, setData] = useState(null);
const [error, setError] = useState(null)
const handleClick = () =>{
fetch('data')
.then((response)=>{
return response.json();
})
.then((json)=>{
setData(json.data)
})
.catch((error)=>{
setError(`Something Wrong: ${error}`)
})
}
const handleClick2 = () => {
fetch('login').then((response) =>{
return response.json()
})
.then((json)=>{
console.log(JSON.stringify(json))
})
}
if(error){
return <p>{error}</p>
}
return (
<div>
<button onClick={handleClick}>데이터 가져오기</button>
<button onClick={handleClick2}>데이터 가져오기2</button>
{data && (
<ul>
{data.people.map((person)=>(
<Item
key={`${person.name}-${person.age}`}
name={person.name}
age={person.age}
/>
))}
</ul>
)}
</div>
);
};
export default TestMocking;
// mocks/handlers.js
import {rest} from 'msw';
export const handlers = [
rest.get('/login', async(req,res,ctx)=>{
return res(
ctx.json({
id: 'f79e82e8-c34a-4dc7-a49e-9fadc0979fda',
firstName: 'John',
lastName: 'Maverick',
})
)
}),
rest.get('/data',async(req,res,ctx) =>{
return res(
ctx.json({
"data": {
"people" :
[
{
"name": "jimmy",
"age": 135
},
{
"name": "timmy",
"age": 13
},
{
"name": "cindy",
"age": 15
},
{
"name": "judy",
"age": 25
},
{
"name": "marry",
"age": 64
},
{
"name": "tommy",
"age": 109
}
]
}
})
)
})
]
// components/TestMocking.jsx
...
const TestMocking = () => {
const [data, setData] = useState(null);
const [error, setError] = useState(null)
const handleClick = () =>{
fetch('data?id=123')
.then((response)=>{
return response.json();
})
.then((json)=>{
setData(json.data)
})
.catch((error)=>{
setError(`Something Wrong: ${error}`)
})
}
...
// mocks/handlers.js
...
rest.get('/data',async(req,res,ctx) =>{
const data = req.url.searchParams.get('id')
return res(
ctx.json({
"data": {
"people" :
[
{
"name": data,
"age": 135
},
{
...