
서비스 워커(Service Worker)를 사용하여 네트워크 호출을 가로채는 API 모킹(mocking) 라이브러리입니다.
axios 모킹에 MSW를 사용하는 이유는 여러 가지입니다.
만약 동시에 두 가지를 요청을 한다면, 모킹 반환값으로는 이를 관리하기 어려울 뿐만 아니라 불안정합니다.
페이지를 다시 정렬하면 모의 화면에서 반환값의 순서가 잘못되어 테스트가 실패할 수도 있습니다.
요청의 POST 데이터 등에 따라 값을 반환하고 싶을 수 있습니다.
MSW라면 핸들러 함수로 가능합니다.
MSW는 쿠키, 이진 응답(예: 이미지) 등 요청의 정교한 측면을 처리할 수 있습니다.
서버 요청 시 사용할 메소드를 바꿀 수 있고, MSW는 어떤 메소드를 사용하든 작동합니다.
간단한 앱에서는 모킹이 가능하지만 더 복잡한 앱에서는 MSW로 다양한 도구를 이용할 수 있습니다.
npm i -D msw
handler 함수를 생성합니다.
handler 함수는 특정 URL, 경로에 어떤 응답이 왔는지를 파악합니다.
테스트 서버를 만듭니다.
테스트 전부를 수행하는 중에 테스트 서버가 계속해서 리스닝하도록 하여 네트워크를 호출을 가로채야 합니다.
테스트 하나가 끝날때 마다 다음 테스트를 위해 서버 핸들러를 리셋합니다.
요청이 들어왔을 때 임의의 응답을 해주는 handler 코드를 작성해야합니다.
Express.js 서버에서 볼 수 있는 코딩 패턴과 상당히 유사한 방식으로 핸들러를 구현할 수 있습니다.
모킹 관련 코드는 프로젝트의 mocks이라는 디렉토리에 두는 것이 일반적인 관례입니다.
HandlerType.HTTPMethod("URL", () => {})
http or graphqlget, post, etc...아래의 예시에서는 http://Full URL to mock로 HTTPMethod요청한 응답으로 { user: { id: "abc-123", name: "John Maverick", }, }을 보내줍니다.
// src/mocks/handlers.js
import { http, HttpResponse } from "msw";
export const handlers = [
http.get("http://Full URL to mock", () => {
// Note that you DON'T have to stringify the JSON!
return HttpResponse.json({
user: {
id: "abc-123",
name: "John Maverick",
},
});
}),
];
HttpResponse 클래스는 Fetch API Response의 대체제로 설계되었으며, 더 편리한 응답 선언과 응답 쿠키 모킹과 같은 특별한 기능을 지원을 목적으로 합니다.
Response는 Fetch API에서 사용되는 객체로 fetch로 요청을 했을 때 웹서버가 응답한 결과를 담고 있는 데이터 입니다.
Response의 주요 속성 (Properties)
Response.status: HTTP 상태 코드를 나타냅니다.
Response.statusText: HTTP 상태 메시지를 나타냅니다.
Response.headers: 응답 헤더를 나타냅니다.
Response.url: 응답이 받아진 URL을 나타냅니다.
Response의 주요 메소드 (Methods)
Response.json(): JSON 형식의 응답을 파싱하여 JavaScript 객체로 반환합니다.
Response.text(): 텍스트 형식의 응답을 문자열로 반환합니다.
Response.blob(): Blob 형식의 응답을 반환합니다.
Response.arrayBuffer(): ArrayBuffer 형식의 응답을 반환합니다.
// HttpResponse 클래스의 생성자
class HttpResponse {
constructor(
body:
| Blob
| ArrayBuffer
| TypedArray
| DateView
| FormData
| ReadableStream
| URLSearchParams
| string
| null
| undefined,
options?: {
status?: number;
statusText?: string;
headers?: HeadersInit;
}
)
}
사용방법은 아래와 같습니다.
new HttpResponse(body, init)
body는 응답의 본문을 나타내고, init은 응답에 대한 옵션을 나타냅니다.
상태코드, 상태 텍스트, 헤더 등을 지정할 수 있습니다.
// 예시: HttpResponse 클래스의 생성자를 사용한 응답 생성
const response = new HttpResponse('Not found', {
status: 404,
headers: {
'Content-Type': 'text/plain',
},
});
JSON 형식의 응답을 생성하는 정적 메소드입니다. 주로 서버 응답 시 JSON 데이터를 사용하는 경우에 활용됩니다.
HttpResponse.json(body, init)
아래 코드는 '/resource' 경로에 대한 JSON 응답을 생성합니다.
"Response.json(body)"와 같습니다.
// 예시: JSON 응답 생성
http.get('/resource', () => {
return HttpResponse.json({
id: 'abc-123',
title: 'Modern Testing Practices',
});
});
네트워크 오류 응답을 생성하는 정적 메소드입니다.
주로 특정 상황에서 네트워크 오류를 시뮬레이트할 때 사용됩니다.
HttpResponse.error()
아래 코드는 "Response.error()"와 같습니다.
'/resource' 경로에 대한 네트워크 오류 응답을 생성합니다.
// 예시: 네트워크 오류 응답 생성
http.get('/resource', () => {
return HttpResponse.error();
});
HttpResponse.error() 및 Response.error() 모두 네트워크 오류 응답을 사용자 정의할 수 없습니다.
사용자 정의하려면 응답 리졸버 내에서 예외를 throw하는 것을 고려하세요.
HttpResponse 클래스는 Fetch API 명세에는 없는 특수한 메소드들도 제공합니다.
HttpResponse.text(body, init)
: 텍스트 응답을 생성하는 메소드로, Content-Type 헤더를 text/plain으로 설정합니다.
HttpResponse.text('Hello world!')
HttpResponse.xml(body, init)
: XML 응답을 생성하는 메소드로, Content-Type 헤더를 application/xml으로 설정합니다.
// 예시: XML 응답 생성
HttpResponse.xml(`
<post>
<id>abc-123</id>
<title>Modern Testing Practices</title>
</post>
`);
HttpResponse.formData(body, init)
: multipart/form-data 형식의 응답을 생성하는 메소드로, FormData를 이용하여 응답을 구성합니다.
// 예시: FormData 응답 생성
const form = new FormData();
form.append('id', 'abc-123');
form.append('title', 'Modern Testing Practices');
HttpResponse.formData(form);
HttpResponse.arrayBuffer(body, init)
: ArrayBuffer를 이용하여 응답을 생성하는 메소드로, Content-Length 헤더를 자동으로 설정합니다.
// 예시: ArrayBuffer 응답 생성
const buffer = new ArrayBuffer(/* byte length */);
HttpResponse.arrayBuffer(buffer);
msw/node 모듈의 setupServer() 함수를 이용하면 테스트용 API 서버를 만들 수 있습니다.
// src/mocks/server.js
import { setupServer } from "msw/node";
import { handlers } from "./handlers";
export const server = setupServer(...handlers);
테스트 실행 전에 가짜 API 서버를 올렸다가 테스트 실행 후에 내릴 수 있도록 Jest 설정을 해줍니다.
// src/setupTests.js
import "@testing-library/jest-dom";
import { beforeAll, afterEach, afterAll } from "vitest";
import { server } from "./src/mocks/node";
// Establish API mocking before all tests
// API 모킹을 하기 위해 제일 먼저 앞서 수행하는 작업
beforeAll(() => server.listen());
// Reset any request handlers that we may add during the tests,
// so they don't affect other tests
// 테스트 간에 핸들러를 리셋하는 코드
afterEach(() => server.resetHandlers());
// Clean up after the tests are finished
// 이 코드는 마지막에 깔끔하게 서버를 끔
afterAll(() => server.close());
handler에 요청을 보낼 테스트 코드를 실행합니다.
서버 연결은 거의 항상 비동기식이기 때문에 await와 findBy를 사용해야 합니다.