Info

  • ํ˜„์žฌ FE๋งŒ ๊ตฌํ˜„๋˜์–ด ์žˆ๋Š” ์ƒํƒœ์ด๊ธฐ ๋•Œ๋ฌธ์— ํ…Œ์ŠคํŠธ๊ฐ€ ํ•„์š”ํ•œ ๊ฒฝ์šฐ, Mock ๋ฐ์ดํ„ฐ๋ฅผ ์‚ฌ์šฉํ•ด์•ผ ํ•จ.
  • ํšจ์œจ์ ์ธ ํ…Œ์ŠคํŠธ๋ฅผ ์œ„ํ•œ ํ™˜๊ฒฝ์„ ๊ณ ๋ คํ•˜๋˜ ์ค‘, MSW(MockServiceWorker)๋ฅผ ์•Œ๊ฒŒ ๋จ.
  • ํ˜„์žฌ Local ํ™˜๊ฒฝ์—์„œ๋Š” MSW๋ฅผ ํ†ตํ•ด Mock ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์˜ค๊ฒŒ ๋˜์–ด ์žˆ์Œ.

MSW (MockServiceWorker)

๋ธŒ๋ผ์šฐ์ € ๋˜๋Š” Node.js ํ™˜๊ฒฝ์—์„œ API ์š”์ฒญ์„ ๊ฐ€๋กœ์ฑ„๊ณ  ๊ฐ€์งœ ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋Š” Mock ์„œ๋ฒ„๋ฅผ ์„ค์ •ํ•  ์ˆ˜ ์žˆ์Œ.

  • ๊ฐ€์ƒ API ์‘๋‹ต: ์‹ค์ œ ์„œ๋ฒ„๊ฐ€ ์—†์–ด๋„ API ํ†ต์‹ ์„ ํ•˜๋Š” ๊ฒƒ๊ณผ ๊ฐ™์ด ๋™์ž‘ ๊ฐ€๋Šฅ. (์‹ค์ œ API ํ†ต์‹  ์†Œ์Šค๋ฅผ ๊ทธ๋Œ€๋กœ ์‚ฌ์šฉ ๊ฐ€๋Šฅ.)
  • ๊ฐœ๋ฐœ ํ…Œ์ŠคํŠธ ์ง€์›: FE ๊ฐœ๋ฐœ ์‹œ, ์‹ค์ œ ์„œ๋ฒ„๊ฐ€ ์—†์–ด๋„ ๋„คํŠธ์›Œํฌ ๊ด€๋ จ ๊ธฐ๋Šฅ์ด๋‚˜ ์œ ๋‹›, ํ†ตํ•ฉ ํ…Œ์ŠคํŠธ ๊ฐ€๋Šฅ.
  • BE ์„œ๋น„์Šค ๋กœ์ง๊ณผ ์œ ์‚ฌํ•˜๊ฒŒ ์„ค์ • ๊ฐ€๋Šฅ: ERROR ์ผ€์ด์Šค๋‚˜ SUCCESS ์ผ€์ด์Šค๋“ค์„ ์‹ค์ œ BE ์„œ๋น„์Šค์™€ ํก์‚ฌํ•˜๊ฒŒ ์„ธํŒ… ๊ฐ€๋Šฅ. (๊ธฐ์กด์— ์ •์˜ํ•œ FE ์†Œ์Šค๋ฅผ ํ™œ์šฉํ•  ์ˆ˜ ์žˆ์Œ.)

Install

npm install msw --save-dev

Setting

  • dev ํ™˜๊ฒฝ์œผ๋กœ ์„œ๋น„์Šค ์‹คํ–‰ ์‹œ, Axios instance ์ƒ์„ฑ ์‹œ์ ์— baseURL: '' ๋ฅผ ํ†ตํ•ด MSW์— ์š”์ฒญ์„ ๋ณด๋‚ด๋„๋ก ์„ค์ •๋จ.

Worker

  • ๊ตฌํ˜„๋œ handler๋กœ Worker๋ฅผ ์‹คํ–‰ํ•˜๋Š” ๊ตฌ๋™๋ถ€.
browser.ts
import { setupWorker } from 'msw/browser'  
import handlers from './handlers'  
  
export const worker = setupWorker(...handlers)

Handlers

  • handlers
    • ๊ตฌํ˜„๋œ handler๋“ค์„ ๋ชจ์•„๋†“์€ ๋‹จ์ˆœํ•œ List
    • handler ์ถ”๊ฐ€ ์‹œ, ํ•ด๋‹น List์— ์ถ”๊ฐ€ ํ•„์š”. (๋ฐ˜๋Œ€๋กœ, ๋‹น์žฅ ์‚ฌ์šฉํ•˜์ง€ ์•Š๋Š” handler๋Š” List์—์„œ ์ œ๊ฑฐ)
handlers.ts
import AuthHandlers from './handlers/AuthHandlers'  
import HandlerInterceptor from './handlers/HandlerInterceptor'  
import ScheduleHandlers from './handlers/ScheduleHandlers'  
import HolidayHandlers from './handlers/HolidayHandlers'  
  
const handlers = [HandlerInterceptor, ...AuthHandlers, ...ScheduleHandlers, ...HolidayHandlers]  
  
export default handlers
  • handler
    • ์‹ค์ œ request๋ฅผ ๋ฐ›์•„ response๋ฅผ ์ˆ˜ํ–‰ํ•˜๋Š” function
    • DB ์ƒํƒœ๋Š” ๊ฐ„๋‹จํ•˜๊ฒŒ variable๋กœ ๋ณด๊ด€
      • ์ƒ์„ฑ ์‹œ์ : ์„œ๋น„์Šค ์‹œ์ž‘ ์‹œ์ 
      • ์ œ๊ฑฐ ์‹œ์ : ์„œ๋น„์Šค ์ข…๋ฃŒ ์‹œ์ 
    • MSW์˜ http, HttpResponse ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ, request๋ฅผ ๋ฐ›๊ณ  response
    • ์‹ค์ œ BE ๋กœ์ง์ฒ˜๋Ÿผ request์—์„œ ๋ฐ›์€ parameter์— ๋”ฐ๋ผ ๋‹ค๋ฅธ ๊ฐ์ฒด๋ฅผ response ๊ฐ€๋Šฅ
      • ์—๋Ÿฌ ๋ฐœ์ƒ์ด ํ•„์š”ํ•œ ์‹œ์ ์— Error ๊ฐ์ฒด๋ฅผ response
      • ์„ฑ๊ณตํ•œ ์‹œ์ ์— ์ผ€์ด์Šค์— ๋”ฐ๋ผ ๋ถ„๋ฆฌํ•˜์—ฌ ๊ฐ๊ธฐ ๋‹ค๋ฅธ ๊ฐ์ฒด๋ฅผ response
ScheduleHandlers.ts
import { http, HttpResponse } from 'msw'  
import { ApiResponse } from '@utils/api/models/ApiResponse'  
import { Schedule } from '@typings/Schedule'  
import { apiCode } from '@utils/error/constant/ApiCode'  
import { scheduleType } from '@typings/constants/ScheduleType'  
import dateFormatUtil from '@utils/date/dateFormatUtil'  
  
let schedules = [  
  new Schedule({  
    id: 1,  
    type: scheduleType.TIME,  
    startedAt: '2025/01/06 00:00',  
    endedAt: '2025/01/10 23:59',  
    title: 'schedule1',  
    contents: 'schedule1',  
    createdAt: '2025/01/06 15:00:00',  
    updatedAt: '2025/01/06 15:00:00',  
  }),  
  new Schedule({  
    id: 2,  
    type: scheduleType.TIME,  
    startedAt: '2025/01/07 07:00',  
    endedAt: '2025/01/07 12:30',  
    title: 'schedule2',  
    contents: 'schedule2',  
    createdAt: '2025/01/06 15:00:00',  
    updatedAt: '2025/01/06 15:00:00',  
  }),  
  new Schedule({  
    id: 3,  
    type: scheduleType.TIME,  
    startedAt: '2025/01/07 08:00',  
    endedAt: '2025/01/07 08:30',  
    title: 'schedule3',  
    contents: 'schedule3',  
    createdAt: '2025/01/06 15:00:00',  
    updatedAt: '2025/01/06 15:00:00',  
  }),  
  new Schedule({  
    id: 4,  
    type: scheduleType.TIME,  
    startedAt: '2025/01/05 04:00',  
    endedAt: '2025/01/05 23:00',  
    title: 'schedule4',  
    contents: 'schedule4',  
    createdAt: '2025/01/06 15:00:00',  
    updatedAt: '2025/01/06 15:00:00',  
  }),  
  new Schedule({  
    id: 5,  
    type: scheduleType.TIME,  
    startedAt: '2025/01/07 04:00',  
    endedAt: '2025/01/09 23:59',  
    title: 'schedule5',  
    contents: 'schedule5',  
    createdAt: '2025/01/06 15:00:00',  
    updatedAt: '2025/01/06 15:00:00',  
  }),  
  new Schedule({  
    id: 6,  
    type: scheduleType.TIME,  
    isImportant: true,  
    startedAt: '2025/01/03 04:00',  
    endedAt: '2025/01/07 23:59',  
    title: 'schedule6',  
    contents: 'schedule6',  
    createdAt: '2025/01/06 15:00:00',  
    updatedAt: '2025/01/06 15:00:00',  
  }),  
  new Schedule({  
    id: 7,  
    type: scheduleType.TIME,  
    startedAt: '2025/01/09 13:00',  
    endedAt: '2025/01/09 14:00',  
    title: 'schedule7',  
    contents: 'schedule7',  
    createdAt: '2025/01/06 15:00:00',  
    updatedAt: '2025/01/06 15:00:00',  
  }),  
  new Schedule({  
    id: 8,  
    type: scheduleType.TIME,  
    startedAt: '2025/01/09 11:00',  
    endedAt: '2025/01/09 17:00',  
    title: 'schedule8',  
    contents: 'schedule8',  
    createdAt: '2025/01/06 15:00:00',  
    updatedAt: '2025/01/06 15:00:00',  
  }),  
  new Schedule({  
    id: 9,  
    type: scheduleType.TIME,  
    startedAt: '2025/01/09 11:00',  
    endedAt: '2025/01/11 17:00',  
    title: 'schedule9',  
    contents: 'schedule9',  
    createdAt: '2025/01/06 15:00:00',  
    updatedAt: '2025/01/06 15:00:00',  
  }),  
  new Schedule({  
    id: 10,  
    type: scheduleType.TIME,  
    startedAt: '2025/02/09 11:00',  
    endedAt: '2025/02/11 17:00',  
    title: 'schedule9',  
    contents: 'schedule9',  
    createdAt: '2025/01/06 15:00:00',  
    updatedAt: '2025/01/06 15:00:00',  
  }),  
  new Schedule({  
    id: 11,  
    type: scheduleType.TASK,  
    startedAt: '2025/02/09 00:00',  
    endedAt: '2025/02/11 23:59',  
    title: 'task1',  
    contents: 'task1',  
    createdAt: '2025/01/06 15:00:00',  
    updatedAt: '2025/01/06 15:00:00',  
  }),  
  new Schedule({  
    id: 12,  
    type: scheduleType.TASK,  
    isImportant: true,  
    startedAt: '2025/01/01 00:00',  
    endedAt: '2025/01/02 23:59',  
    title: 'task2',  
    contents: 'task2',  
    createdAt: '2025/01/06 15:00:00',  
    updatedAt: '2025/01/06 15:00:00',  
  }),  
]  
  
const ScheduleHandlers = [  
  http.get<  
    never,  
    | {  
        year?: number  
        month?: number  
        day?: number  
      }  
    | undefined,  
    null | ApiResponse<Schedule[]>  
  >('/schedule', ({ request }) => {  
    const { stringToDate } = dateFormatUtil  
    const date = new URL(request.url).searchParams  
    const year = Number(date.get('year'))  
    const month = Number(date.get('month'))  
    const day = Number(date.get('day'))  
  
    if (day) {  
      return HttpResponse.json(  
        new ApiResponse<Schedule[]>().build(  
          apiCode.SUCCESS,  
          schedules.filter(schedule => {  
            const targetDate = stringToDate(schedule.startedAt)  
            return (  
              targetDate.year() == year &&  
              targetDate.month() + 1 == month &&  
              targetDate.date() == day  
            )  
          }),  
        ),  
      )  
    }  
    if (month) {  
      return HttpResponse.json(  
        new ApiResponse<Schedule[]>().build(  
          apiCode.SUCCESS,  
          schedules.filter(schedule => {  
            const targetDate = stringToDate(schedule.startedAt)  
            return targetDate.year() == year && targetDate.month() + 1 == month  
          }),  
        ),  
      )  
    }  
    if (year) {  
      return HttpResponse.json(  
        new ApiResponse<Schedule[]>().build(  
          apiCode.SUCCESS,  
          schedules.filter(schedule => stringToDate(schedule.startedAt).year() == year),  
        ),  
      )  
    }  
  
    return HttpResponse.json(new ApiResponse<Schedule[]>().build(apiCode.SUCCESS, schedules))  
  }),  
  
  http.post<never, Schedule, ApiResponse<number | null>>('/schedule', async ({ request }) => {  
    const schedule: Schedule = await request.json()  
  
    if (!schedule)  
      return HttpResponse.json(new ApiResponse<null>().build(apiCode.INVALID_REQUEST_PARAM, null))  
  
    const id = schedules.length + 1  
    schedule.id = id  
    schedules.push(schedule)  
  
    return HttpResponse.json(new ApiResponse<number>().build(apiCode.SUCCESS, id))  
  }),  
  
  http.delete<{ id: string }, null, null>('/schedule/:id', ({ params }) => {  
    const id = Number(params.id)  
    if (!id)  
      return HttpResponse.json(new ApiResponse<null>().build(apiCode.INVALID_REQUEST_PARAM, null))  
    schedules = schedules.filter(schedule => schedule.id != id)  
    return HttpResponse.json(new ApiResponse<number>().build(apiCode.SUCCESS, id))  
  }),  
  
  http.put<{ id: string }, Schedule, null>('/schedule/:id', async ({ params, request }) => {  
    const id = Number(params.id)  
    const schedule = await request.json()  
    if (!id || !schedule)  
      return HttpResponse.json(new ApiResponse<null>().build(apiCode.INVALID_REQUEST_PARAM, null))  
  
    schedules = schedules.map(stateSchedules =>  
      stateSchedules.id == id ? schedule : stateSchedules,  
    )  
    return HttpResponse.json(new ApiResponse<null>().build(apiCode.SUCCESS, null))  
  }),  
]  
  
export default ScheduleHandlers