가수면
msw 본문
실제 요청을 가로채는 API 목킹 라이브러리
npm install msw --save-dev
기본 세팅
handlers 설정
요청할 api들을 모아 놓은 곳. 원하는 결과를 설정해 넣으면 테스트 시 설정한 대로 요청이 간다.
// src\mocks\handlers.ts
import { rest } from "msw";
export const handlers = [
rest.get("http://localhost:3030/scoops", (req, res, ctx) => {
return res(
ctx.json([
{ name: "Chocolate", imagePath: "/images/chocolate.png" },
{ name: "Vanilla", imagePath: "/images/vanilla.png" },
])
);
}),
rest.get("http://localhost:3030/toppings", (req, res, ctx) => {
return res(ctx.status(200));
}),
];
server.js 설정
설정한 api 모음을 모킹 서버로 셋업
// src\mocks\server.js
import { setupServer } from 'msw/node';
import { handlers } from './handlers';
// This configures a request mocking server with the given request handlers.
export const server = setupServer(...handlers);
Create React App 했을 시
아래처럼 설정
// src/setupTests.js
import { server } from './mocks/server.js'
// Establish API mocking before all tests.
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())
그 외
jest.setup.js를 만들어 설정한다.
touch jest.setup.js
beforeAll(() => server.listen());
afterEach(() => server.resetHandlers());
afterAll(() => server.close())
를 테스트 파일 안에 사용
axios 오류 해결
SyntaxError: Cannot use import statement outside a module
Jest는 Node.js 환경에서 사용되는 테스트 프레임워크다.
그러나 애플리케이션이 웹 클라이언트용으로 빌드되고, axios가 버전업이 되면서 CommonJS 대신 ES 모듈로 빌드되도록 변경되었기 때문에 CommonJS 형식으로 작성된 Node.js에서 실행되지 않는 문제가 발생한 것이다.
(모듈 외부에서 import 문을 사용할 수 없다며 axios import하는 부분에 오류가 나는 이유)
해결 방법으로는 여러가지들이 있다.
1. axios를 0.27버전으로 다운그레이드 한다. (현재 npm을 통해 설치되는 버전은 1.4다...)
npm install axios@^0.27
2. jest, @testing-library/react를 최신 버전으로 업데이트한다. (@testing-library/jest-dom 설치는 권장 사항)
3. jest와 바벨의 설정을 수정한다.
4-1. axios를 CJS 모듈로 가져 오기를 강제한다.
"scripts": {
"test": "react-scripts test --transformIgnorePatterns \"node_modules/(?!axios)/\"",
},
4-2. axios를 CJS 모듈로 가져 오기를 강제한다. (이 방법도 안 됐다.)
// package.json
"jest": {
"moduleNameMapper": {
"axios": "axios/dist/node/axios.cjs"
}
}
내 경우엔 @testing-library/react를 버전 업하고 4-1을 사용하는 방법으로 해결할 수 있었다.
mock api 쿼리 스트링
msw는 쿼리 스트링을 사용하는 url과 사용하지 않는 url을 구분하고 있지 않기 때문에 로직으로 구분해줘야한다.
먼저 예시를 보자.
rest.get("/keywords?type=me", (req, res, ctx) => {
.
.
rest.get("/keywords", (req, res, ctx) => {
만약 위처럼 mock api를 구성할 경우 "/keywords?type=me" 요청이 "/keywords"요청을 덮어쓰게 된다.
이런 경우 아래처럼 로직으로 구분해줘야 한다.
rest.get("/keywords", (req, res, ctx) => {
const type = req.url.searchParams.get("type");
if (type === "me") {
.
.
.
} else {
실제 환경에서 리액트 쿼리의 invalidateQueries 사용 시 테스트
예를 들어 무언가를 추가할 때 useMutation의 onSuccess로 invalidateQueries를 이용해 get요청을 refetch한다면, msw에서 다음과 같이 res값을 고정된 값으로 get요청을 설정했을 때 refetch가 되어도 고정된 값을 받아와 추가된 것이 반영되지 않으므로 테스트가 실패하게 된다.
rest.get("/keywords", (req, res, ctx) => {
return res(
ctx.status(200),
ctx.json([
"리액트네이티브",
"리액트네이티브 ios",
"리액트네이티브 애니메이션",
"리액트네이티브 플러터 차이",
"리액트네이티브 튜토리얼",
]),
);
}),
rest.post("/keywords", (req, res, ctx) => {
return res(ctx.status(200));
}),
이 경우 다음과 같은 방법으로 해결할 수 있다.
const myKeyword = [
"리액트네이티브",
"리액트네이티브 ios",
"리액트네이티브 애니메이션",
"리액트네이티브 플러터 차이",
"리액트네이티브 튜토리얼",
];
//
rest.get("/keywords", (req, res, ctx) => {
return res(
ctx.status(200),
ctx.json(myKeyword),
);
}),
rest.post("/keywords", async (req, res, ctx) => {
const { keyword } = await req.json();
myKeyword.push(keyword);
return res(ctx.status(200));
}),
use()
현재 서버 인스턴스에 다른 테스트에도 영향을 미치는 요청을 추가 설정한다.
기존 핸들러와 겹치는 경우(동일한 술어를 가짐) 항상 런타임 핸들러가 우선하며, 영구히 대체한다.
import { rest } from 'msw'
import { setupServer } from 'msw/node'
const server = setupServer(
// Initial request handler for a book detail.
rest.get('/book/:bookId', (req, res, ctx) => {
return res(ctx.json({ title: 'Lord of the Rings' }))
}),
)
test('handles server error gracefully', () => {
server.use(
// Runtime request handler override for the "GET /book/:bookId".
rest.get('/book/:bookId', (req, res, ctx) => {
return res(ctx.json({ title: 'A Game of Thrones' }))
}),
)
// Any requests to "GET /book/:bookId" will return
// the "A Game of Thrones" mocked response from
// the runtime request handler override.
})
일회성으로 다음 요청만 재정의 하려면 return 값에 res.once를 사용하면 된다.
const server = setupServer(
// Initial request handler for a book detail.
rest.get('/book/:bookId', (req, res, ctx) => {
return res(ctx.json({ title: 'Lord of the Rings' }))
}),
)
test('handles server error gracefully', () => {
server.use(
rest.get('/book/:bookId', (req, res, ctx) => {
// The first matching "GET /book/:bookId" request
// will receive this mocked response.
return res.once(
ctx.status(500),
ctx.json({ message: 'Internal server error' }),
)
}),
)
// Request to "GET /book/:bookId" in this test will return
// a mocked error response (500).
})
test('renders a book detail', () => {
// Request to "GET /book/:bookId" in this test will return
// a mocked "Lord of the Rings" book detail.
})
resetHandlers()
handlers에 정의한 모든 api 요청을 재설정한다.
필요한 곳에 resetHandlers을 통해 api를 사용하면 해당 테스트는 디폴트로 설정한 api요청을 덮어씌운다.
const server = setupServer(rest.get('/book/:bookId', bookDetailResolver))
test('first test', () => {
// "GET /book/:bookId" 사용할 수 있음
})
test('second test', () => {
server.resetHandlers(rest.post('/login', loginResolver))
// "POST /login" 만 사용 가능함
})
'React > 테스팅 라이브러리' 카테고리의 다른 글
Playwright 기본 (0) | 2024.04.24 |
---|---|
테스트 종료와 비동기 업데이트 충돌 오류 (0) | 2023.06.27 |
테스팅 라이브러리 심화 (0) | 2023.06.26 |
user-event (0) | 2023.06.23 |
테스팅 쿼리 (0) | 2023.06.22 |