msw(mock service worker)의 역할
서버 주소로 api 요청을 보내야 하는데, 그 서버 주소로 보내는 요청을 mockServiceWorker가 가로채서 mockServiceWorker가 응답을 준다.
msw(mock service worker)를 사용하는 이유
사용하는 이유 ? 예전에는 개발 환경용 주소, 실제 환경 주소를 분기 처리 했어야 했다.
예를 들면 process.env.NODE_ENV === “development” 로 개발 환경일 때 api 주소를 변경하는 조건을 사용해 분기 처리해야 했음. 그러나 msw를 사용하면 분기 처리 하지 않고 실제 주소로 api 호출을 하면 개발 서버에서만 msw를 활성화시켜서 서버 주소로 보내는 요청을 가로챌 수 있다.
msw로 자주 쓰이는 예시
- api가 만들어지지 않았을 경우 가짜 api를 만들때
- msw의 response를 사용하여, 실제 에러 상황을 클라이언트에서 재현하지 않아도 msw에서 에러 응답을 줘서 테스트할 수 있다.
- 백엔드 요청 없이도 프론에서 응답 테스트를 할 수 있다.
msw 기반 코드 자동 생성
public에 msw기반 코드를 자동 생성한다
npm install msw@latest --save-dev
Enter 후 Ok to proceed? (y) y 입력 후 Enter
설치 완료되면 아래 사항이 추가됨.
- package.json에 msw 관련 설정 추가
- public 폴더에 mockServiceWorker.js 파일이 추가됨
mock service worker 설치 및 파일 추가
설치
npm 사용 시
npx msw init public/ --save
yarn 사용 시
yarn add msw@latest --save-dev
참고 : mock service worker
파일 추가
- src/mock/browser.ts : 브라우저 환경 설정
import { setupWorker } from 'msw/browser'
import { handlers } from './handlers'
// This configures a Service Worker with the given request handlers.
const worker = setupWorker(...handlers)
export default worker;
- src/mock/handlers.ts : 요청을 mocking 하는 핸들러 작성
import {http, HttpResponse, StrictResponse} from 'msw'
import {faker} from "@faker-js/faker";
function generateDate() {
const lastWeek = new Date(Date.now());
lastWeek.setDate(lastWeek.getDate() - 7);
return faker.date.between({
from: lastWeek,
to: Date.now(),
});
}
const User = [
{id: 'elonmusk', nickname: 'Elon Musk', image: '/yRsRRjGO.jpg'},
{id: 'leoturtle', nickname: '레오', image: faker.image.avatar()},
]
const Posts = [];
export const handlers = [
http.post('/api/login', () => {
console.log('로그인');
return HttpResponse.json(User[1], {
headers: {
'Set-Cookie': 'connect.sid=msw-cookie;HttpOnly;Path=/'
}
})
}),
http.post('/api/logout', () => {
console.log('로그아웃');
return new HttpResponse(null, {
headers: {
'Set-Cookie': 'connect.sid=;HttpOnly;Path=/;Max-Age=0'
}
})
}),
http.post('/api/users', async ({ request }) => {
console.log('회원가입');
// return HttpResponse.text(JSON.stringify('user_exists'), {
// status: 403,
// })
return HttpResponse.text(JSON.stringify('ok'), {
headers: {
'Set-Cookie': 'connect.sid=msw-cookie;HttpOnly;Path=/;Max-Age=0'
}
})
}),
http.get('/api/postRecommends', ({ request }) => {
const url = new URL(request.url)
const cursor = parseInt(url.searchParams.get('cursor') as string) || 0
return HttpResponse.json(
[
{
postId: cursor + 1,
User: User[0],
content: `${cursor + 1} Z.com is so marvelous. I'm gonna buy that.`,
Images: [{imageId: 1, link: faker.image.urlLoremFlickr()}],
createdAt: generateDate(),
},
{
postId: cursor + 2,
User: User[0],
content: `${cursor + 2} Z.com is so marvelous. I'm gonna buy that.`,
Images: [
{imageId: 1, link: faker.image.urlLoremFlickr()},
{imageId: 2, link: faker.image.urlLoremFlickr()},
],
createdAt: generateDate(),
},
{
postId: cursor + 3,
User: User[0],
content: `${cursor + 3} Z.com is so marvelous. I'm gonna buy that.`,
Images: [],
createdAt: generateDate(),
},
{
postId: cursor + 4,
User: User[0],
content: `${cursor + 4} Z.com is so marvelous. I'm gonna buy that.`,
Images: [
{imageId: 1, link: faker.image.urlLoremFlickr()},
{imageId: 2, link: faker.image.urlLoremFlickr()},
{imageId: 3, link: faker.image.urlLoremFlickr()},
{imageId: 4, link: faker.image.urlLoremFlickr()},
],
createdAt: generateDate(),
},
{
postId: cursor + 5,
User: User[0],
content: `${cursor + 5} Z.com is so marvelous. I'm gonna buy that.`,
Images: [
{imageId: 1, link: faker.image.urlLoremFlickr()},
{imageId: 2, link: faker.image.urlLoremFlickr()},
{imageId: 3, link: faker.image.urlLoremFlickr()},
],
createdAt: generateDate(),
},
]
)
}),
http.get('/api/followingPosts', ({ request }) => {
return HttpResponse.json(
[
{
postId: 1,
User: User[0],
content: `${1} Stop following me. I'm too famous.`,
Images: [{imageId: 1, link: faker.image.urlLoremFlickr()}],
createdAt: generateDate(),
},
{
postId: 2,
User: User[0],
content: `${2} Stop following me. I'm too famous.`,
Images: [{imageId: 1, link: faker.image.urlLoremFlickr()}],
createdAt: generateDate(),
},
]
)
}),
http.get('/api/search/:tag', ({ request, params }) => {
const { tag } = params;
return HttpResponse.json(
[
{
postId: 1,
User: User[0],
content: `${1} 검색결과 ${tag}`,
Images: [{imageId: 1, link: faker.image.urlLoremFlickr()}],
createdAt: generateDate(),
},
{
postId: 2,
User: User[0],
content: `${2} 검색결과 ${tag}`,
Images: [{imageId: 1, link: faker.image.urlLoremFlickr()}],
createdAt: generateDate(),
},
]
)
}),
http.get('/api/users/:userId/posts', ({ request, params }) => {
const { userId } = params;
return HttpResponse.json(
[
{
postId: 1,
User: User[0],
content: `${1} ${userId}의 게시글`,
Images: [{imageId: 1, link: faker.image.urlLoremFlickr()}],
createdAt: generateDate(),
},
{
postId: 2,
User: User[0],
content: `${2} ${userId}의 게시글`,
Images: [{imageId: 1, link: faker.image.urlLoremFlickr()}],
createdAt: generateDate(),
},
]
)
}),
http.get('/api/users/:userId', ({ request, params }): StrictResponse<any> => {
const {userId} = params;
const found = User.find((v) => v.id === userId);
if (found) {
return HttpResponse.json(
found,
);
}
return HttpResponse.json({ message: 'no_such_user' }, {
status: 404,
})
}),
http.get('/api/posts/:postId', ({ request, params }): StrictResponse<any> => {
const {postId} = params;
if (parseInt(postId as string) > 10) {
return HttpResponse.json({ message: 'no_such_post' }, {
status: 404,
})
}
return HttpResponse.json(
{
postId,
User: User[0],
content: `${1} 게시글 아이디 ${postId}의 내용`,
Images: [
{imageId: 1, link: faker.image.urlLoremFlickr()},
{imageId: 2, link: faker.image.urlLoremFlickr()},
{imageId: 3, link: faker.image.urlLoremFlickr()},
],
createdAt: generateDate(),
},
);
}),
http.get('/api/posts/:postId/comments', ({ request, params }) => {
const { postId } = params;
return HttpResponse.json(
[
{
postId: 1,
User: User[0],
content: `${1} 게시글 ${postId}의 답글`,
Images: [{imageId: 1, link: faker.image.urlLoremFlickr()}],
createdAt: generateDate(),
},
{
postId: 2,
User: User[0],
content: `${2} 게시글 ${postId}의 답글`,
Images: [{imageId: 1, link: faker.image.urlLoremFlickr()}],
createdAt: generateDate(),
},
]
)
}),
http.get('/api/followRecommends', ({ request}) => {
return HttpResponse.json(User);
}),
http.get('/api/trends', ({ request }) => {
return HttpResponse.json(
[
{tagId: 1, title: '제로초', count: 1264},
{tagId: 2, title: '원초', count: 1264},
{tagId: 3, title: '투초', count: 1264},
]
)
}),
];
- src/mock/http.ts : mocking 된 응답을 반환하는 핸들러 설정
import { createMiddleware } from '@mswjs/http-middleware';
import express from 'express';
import cors from 'cors';
import { handlers } from './handlers';
const app = express();
const port = 9090;
app.use(cors({ origin: 'http://localhost:3000', optionsSuccessStatus: 200, credentials: true }));
app.use(express.json());
app.use(createMiddleware(...handlers));
app.listen(port, () => console.log(`Mock server is running on port: ${port}`));
mock service worker 사용을 위한 기본 세팅 끝!
728x90
'개발 > React, NextJs' 카테고리의 다른 글
CSR과 SSR에 대해 알아보자 (0) | 2024.04.18 |
---|---|
Virtual Dom이란 무엇인가? (0) | 2024.04.02 |
Next.js의 App Router와 Pages Router (0) | 2024.03.20 |