Mock Service Worker
(이하 MSW) 를 사용하기 위해서 기본적으로 작성해야 할 코드에 대해서는 이전 포스팅에서 작성했다.
이번에는 실제로 MSW 핸들러 코드를 작성하는 법을 작성해보고자 한다.
또 MSW 를 적용하는 것은 쉽지만, 타입스크립트를 적용하는 부분에서 애먹었던 부분이 있었다.
그래서 타입스크립트를 적용하는 방법도 함께 작성해보고자 한다.
handler.ts
작성하기import { rest } from "msw";
export const handlers = [
// ...
]
handers.ts
파일은 기본적으로 이렇게 배열의 형태다.
물론 배열이 아니라 한 개의 핸들러만 작성할 수 있지만, 보통은 여러개를 작성할 수 밖에 없기 때문에 배열로 작성하게 된다.
그럼 여기에 실제로 api 모킹 핸들러를 추가해보자
export const handlers = [
rest.get('/', async (req, res, ctx) => {
return res(
// ...
)
}),
]
차근차근 따라가보자
이렇게 하나의 핸들러를 등록할 수 있다. rest 방식과 graphQl 방식 중 나는 더 익숙한 rest 방식으로 진행하겠다.
rest 방식으로 핸들러를 작성한다면, rest
를 쓰고 그 뒤에는 GET
, POST
, PATCH
, DELETE
등의 HTTP 메서드를 붙인다.
Ex) rest.get()
, rest.post()
MSW 의 핸들러는 기본적으로 1) 어떤 URL 요청이 들어왔을 때 2) 어떤 동작을 수행할지 로 이루어져있다.
1) 따라서 첫번째 인자는 어떤 URL 로 들어왔을 때를 말하는 URL 경로가 오고,
2) 두번째 인자는 그걸 처리하는 로직이 들어온다.
(Next 환경에서는 '/'
만 적으면 제대로 경로설정이 안되는 문제가 있었다. 왜인지는 모르겠지만.. 그래서 Next 에서는 'http://localhost:3000/' 까지 적어주어야 했다.)
export const handlers = [
rest.get('/', async (req, res, ctx) => {
return res(
ctx.json({
message: "Welcome to 멋쟁이 토마토처럼🍅",
})
)
}),
]
두번째 인자로 들어오는 콜백함수의 매개변수로는 req
, res
, ctx
가 온다.
req
는 흔히 말하는 '요청'에 관한 역할을 처리한다.
res
는 응답을 리턴할 때 쓰이는 함수라고 볼 수 있다.
ctx
는 실제로 응답을 어떻게 처리할지 세세한 내용을 정해줄 수 있다.
위의 코드는 '/'
루트 경로로 요청이 들어왔을 때 리턴으로 프로퍼티 키가 message
이고, value 가 Welcome to 멋쟁이 토마토처럼🍅
인 객체를 json 형태로 리턴하겠다는 코드다.
아주아주 간단한 구조다. 이제 저 ctx.json()
코드 안에 내가 원하는 응답을 json 형태로 보내주면 되는 것이다. 배열이 들어갈 수도 있고, 객체가 와도 되고, 바로 어떤 값을 넣어줄 수도 있다.
이 점이 MSW 의 강력한 기능이다.
해당 URL 로 온 요청에 대해서는 내가 의도한 응답을 내려줄 수 있다. 그런데 그것이 기존의 mock data 를 준비해놓고서 프로젝트를 진행하는 것이 아니라, 실제로 api 요청을 보내고 응답을 보내는 과정 속에서 mock data 를 내려주는 것이기 때문에 네트워크 단에서 모킹을 한다는 점이 차이가 있다.
나중에 백엔드에서 api 가 완성이 된다면 (그리고 모킹할 때의 응답과 똑같이 온다면) 별다른 수정 없이 MSW 만 프로젝트에서 제거한다면 그대로 백엔드 코드와 함께 돌아간다는 것이다.
export const handlers = [
rest.get('/', async (req, res, ctx) => {
return res(
ctx.status(200),
ctx.delay(2000),
ctx.json({
message: "Welcome to 멋쟁이 토마토처럼🍅",
})
)
}),
]
그 외에도 상태코드도 커스텀할 수 있고, 비동기 로직처럼 동작하게 하기 위해 약간의 딜레이를 밀리초(ms) 로 추가할 수도 있다.
이번엔 POST 방식이다. 로그인을 예로 들어보자
export const handlers = [
rest.post('/login', async (req, res, ctx) => {
const { email, password } = req.body
const finded = uesrs.find(user => user.email === email)
if (!finded) {
return res(
ctx.status(401)
)
}
return res(
ctx.status(200),
ctx.delay(2000),
ctx.json({
id: finded.id,
email: finded.email,
name: finded.name,
profileImg: finded.profileImg,
})
)
}),
]
간단하게 로그인은 이러한 방식으로 처리할 수 있다.
요청을 보내는 부분에서 POST
로 요청을 보낼 때 해당 데이터를 함께 보내게 된다.
위 예시에서는 POST
로 email 과 password 를 보낸다면 핸들러에선 req.body.email, req.body.password 로 꺼내쓸 수 있게 된다.
위의 users 는 로그인 기능 구현을 위해 간단하게 준비한 더미데이터다.
이런 식으로 이메일이 일치하는 이메일이 없다면 에러 상태코드를 내보낼 수도 있고, 로그인이 성공했다면 또 다시 원하는 응답을 내려줄 수 있다.
타입스크립트로 MSW 로 작성할 때 req.body 에 들어있는 email 과 password 의 타입을 모르기 때문에 에러를 내보내게 된다. 따라서 이 둘의 타입을 지정해주어야 한다.
interface PostLoginReqBody {
email: string
password: string
}
export const handlers = [
rest.post<PostLoginReqBody>('/login', async (req, res, ctx) => {
const { email, password } = req.body
// 이하 동일
}),
]
이렇게 rest.post
뒤에 제너릭 안에 email 과 password 의 타입을 적어준 인터페이스를 넣어줌으로써 req.body 가 어떤 타입으로 오는지를 명시해준다.
그러면 타입스크립트가 req.body 안에 어떤 데이터와 타입이 오는지를 추론할 수 있다.
api 요청을 보내다보면 달라지는 파라미터에 대해서 추출해야할 때가 있다.
가령 아래와 같은 요청이 들어왔다고 치자
export const handlers = [
rest.get('/:userId', async (req, res, ctx) => {
const { userId } = req.params
// ...
}),
]
위에처럼 파라미터로 :userId
가 들어온다고 치면, MSW 의 핸들러 내부에선 req.params.userId 로 꺼내쓸 수 있다.
req.params 의 경우는 타입을 따로 지정해주지 않아도 req.params.파라미터로 온 것들은 모두 문자열로 인식한다.
때문에 params 로 숫자타입을 보내도 문자타입으로 변환되기 때문에 적절히 타입변환을 해서 사용하면 된다.
이상 간단하게 MSW 를 사용하는 법에 대해 알아보았다.
이 정도만 돼도 간단한 api 모킹을 사용하는 데에는 충분히 처리할 수 있다.
그 외에 세세한 부분은 MSW 공식문서 를 확인하면서 적용하면 충분할 것이다.