API 호출을 조롱하거나 스터빙하는 단위 테스트를 작성하는 것은 부담스러울 수 있습니다. 저도 그런 경험이 있습니다. 이 글에서는 테스트 중에 API 요청을 모의하는 더 간단하고 효율적인 방법을 안내하겠습니다.
시작하기 전에 사용할 도구 목록은 다음과 같습니다.
모든 것이 익숙하지 않더라도 걱정하지 마세요. 따라만 하시면 각 단계를 안내해 드리겠습니다.
프로젝트에 대한 Github 링크는 다음과 같습니다
개요
우리는 Json Placeholder API를 사용하여 Todos 목록을 가져와 표시하는 간단한 React 앱을 구축할 것입니다. 이 프로젝트에서는 다음 사항을 확인하기 위해 테스트 시나리오를 작성합니다.
이 가이드에서는 두 가지 주요 접근 방식을 다룹니다.
시작해 보세요!
앱 구축을 시작하려면 다음 단계를 따르세요.
1. 새 React 앱 만들기: 다음 명령을 실행하여 React 애플리케이션을 만듭니다.
npx create-react-app msw-example
2. 애플리케이션 시작: 설정 후 프로젝트 폴더로 이동하여 다음을 실행합니다.
npm start
3. 필요한 패키지 설치: 다음으로 @tanstack/react-query를 설치하여 클라이언트측 데이터를 관리합니다.
npm install @tanstack/react-query
React Query(Tanstack Query)는 캐싱, 동기화, 데이터 가져오기 등 서버 측 상태 관리를 처리하는 데 도움이 됩니다. 공식 문서에서 이에 대해 자세히 알아볼 수 있습니다
이제 앱 로직 작성을 시작하고 React Query를 설정하여 데이터 가져오기를 효율적으로 관리할 수 있습니다. 설정 후의 모습은 이렇습니다.
import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; import { Todos } from "components/Todos"; const queryClient = new QueryClient(); function App() { return ( <QueryClientProvider client={queryClient}> <div data-testid="app" className="App"> <Todos /> </div> </QueryClientProvider> ); } export default App;
다음으로 할 일 목록을 렌더링하는 Todos 구성 요소를 만듭니다.
Todos.js
import { useQuery } from "@tanstack/react-query"; import { getTodos } from "api/todo"; export function Todos() { const { data, isError, isLoading } = useQuery({ queryFn: getTodos, queryKey: ["TODOS"], }); if (isLoading) { return <p>loading todo list</p>; } if (isError) { return <p>an error occurred fetching todo list</p>; } return ( <div> {Boolean(data.length) ? ( <ol> {data.map((item) => ( <li key={item.id}>{item.title}</li> ))} </ol> ) : ( <p>You do not have todos created yet</p> )} </div> ); }
이제 모든 것이 설정되었으므로 앞서 설명한 시나리오에 대한 테스트 작성을 시작하겠습니다. 먼저, 우리에게 이미 익숙한 전통적인 접근 방식, 즉 Jest를 사용하여 API 호출을 모의하는 방법을 사용하여 이를 구현하겠습니다
따라갈 수 있도록 github 저장소를 확인하세요
API 호출을 모의하는 전통적인 방법
React와 Jest는 기본적으로 함께 원활하게 작동하므로 적어도 현재로서는 추가 구성이나 설정이 필요하지 않습니다. Todo.js 구성 요소 옆에 Todos.test.js라는 파일을 생성하고 여기서 Todos 구성 요소를 가져와서 테스트를 작성합니다..
할일 목록을 검색하고 응답을 반환하는 API 호출을 담당하는 getTodos라는 함수가 있습니다.
export async function getTodos() { const response = await fetch("https://jsonplaceholder.typicode.com/todos", { headers: { "Content-Type": "application/json", }, }); if (!response.ok) { const res = await response.json(); throw new Error(res.message || response.status); } return response.json(); }
Todos.test.js 파일에서 Todos 구성 요소를 가져오고 React 쿼리 공급자와 함께 래퍼를 제공하는 유틸리티 함수를 생성해야 합니다. 이렇게 하면 Todos 구성 요소와 해당 하위 요소가 테스트에서 서버 상태를 관리하기 위해 반응 쿼리를 사용할 수 있습니다.
import { render, screen, waitFor, within } from "@testing-library/react"; import { Todos } from "./Todos"; import { reactQueryWrapper } from "utils/reactQueryWrapper"; import { getTodos } from "api/todo"; const { wrapper, queryCache } = reactQueryWrapper();
다음으로 getTodos 함수를 모의해야 합니다. 이를 통해 각 테스트 시나리오에 대한 반환 값을 지정할 수 있으므로 테스트 중에 함수가 반환하는 데이터를 제어할 수 있습니다. 또한 이전 테스트 사례에서 남은 데이터를 모두 정리하여 각 테스트 사례가 깨끗한 상태에서 시작되도록 할 것입니다.
코드 샘플
jest.mock("api/todo", () => ({ getTodos: jest.fn(), })); afterEach(() => { queryCache.clear(); jest.clearAllMocks(); });
Todos.test.js
import { render, screen, waitFor, within } from "@testing-library/react"; import { Todos } from "./Todos"; import { reactQueryWrapper } from "utils/reactQueryWrapper"; import { getTodos } from "api/todo"; const { wrapper, queryCache } = reactQueryWrapper(); jest.mock("api/todo", () => ({ getTodos: jest.fn(), })); afterEach(() => { queryCache.clear(); }); afterEach(() => { jest.clearAllMocks(); });
첫 번째 테스트 시나리오: 렌더링 로드 상태
요청이 진행되는 동안 구성 요소가 로드 상태를 올바르게 표시하는지 확인하고 싶습니다.
test("Renders loading state", () => { getTodos.mockImplementation(() => { return new Promise((resolve) => { setTimeout(() => { resolve(); }, 1000); }); }); render(<Todos />, { wrapper }); const loadingText = screen.getByText("loading todo list"); expect(loadingText).toBeInTheDocument(); });
두 번째 테스트 시나리오: 요청이 실패하거나 네트워크 오류가 있는 경우 오류 상태를 렌더링
우리는 API 요청이 실패하거나 네트워크 오류가 발생할 때 구성 요소가 오류 상태를 올바르게 렌더링하는지 확인하고 싶습니다.
test("Renders error state when request fails or there is network error", async () => { getTodos.mockImplementationOnce(() => { return new Promise((resolve, reject) => { reject(); }); }); render(<Todos />, { wrapper }); await screen.findByText("an error occurred fetching todo list"); expect( screen.getByText("an error occurred fetching todo list") ).toBeInTheDocument(); });
세 번째 테스트 시나리오: 할 일 목록 렌더링
요청이 성공하면 구성 요소가 할 일 목록을 올바르게 렌더링하는지 확인하고 싶습니다.
test("Renders list of todos", async () => { getTodos.mockImplementation(() => { return Promise.resolve([ { id: 1, title: "Exercise" }, { id: 2, title: "Cook" }, ]); }); render(<Todos />, { wrapper }); const loadingText = screen.queryByText("loading todo list"); await waitFor(() => expect(loadingText).not.toBeInTheDocument()); const list = screen.getByRole("list"); expect(list).toBeInTheDocument(); expect(within(list).getAllByRole("listitem")).toHaveLength(2); });
네 번째 테스트 시나리오: 할 일 목록 렌더링
API 요청이 빈 할 일 목록을 반환할 때 구성 요소가 피드백 메시지를 올바르게 렌더링하는지 확인하고 싶습니다.
test("Renders feedback message when user has an empty list of todos", async () => { getTodos.mockImplementationOnce(() => { return Promise.resolve([]); }); render(<Todos />, { wrapper }); await waitFor(() => expect(screen.queryByText("loading todo list")).not.toBeInTheDocument() ); expect( screen.getByText("You do not have todos created yet") ).toBeInTheDocument(); });
Great! Now that we've covered mocking API calls with Jest, let’s explore a better approach using Mock Service Worker (MSW). MSW provides a more elegant and maintainable way to mock API calls by intercepting network requests at the network level rather than within your tests.
Introducing MSW (Mock Service Worker)
Mock Service Worker (MSW) is an API mocking library designed for both browser and Node.js environments. It allows you to intercept outgoing requests, observe them, and provide mocked responses. MSW helps you simulate real-world scenarios in your tests, making them more robust and reliable.
Read more about MSW
Setting Up MSW
Step 1: Install MSW using the following command:
npm install msw@latest --save-dev
Step 2: Set up the environment you wish to intercept requests in—either Browser or Node. Before doing so, create a mock directory within your src directory. Inside this directory, you'll create the following files and directories:
browser.js: Handles request interception in the browser environment.
server.js: Handles request interception in the Node.js environment.
handlers: A directory containing files that define the API endpoints to intercept.
Here’s how your folder structure should look:
src/ └── mock/ ├── browser.js ├── server.js └── handlers/
This setup ensures that you have a clear organization for intercepting and handling requests in both browser and Node.js environments using MSW.
Browser Environment Setup
To set up MSW for intercepting requests in the browser, follow these steps:
1. Create the browser.js File
In your src/mock directory, create a file named browser.js. This
file will set up the MSW worker to intercept requests in the
browser environment.
// src/mock/browser.js import { setupWorker } from 'msw/browser'; // Create a worker instance to intercept requests export const worker = setupWorker();
2. Generate the mockServiceWorker.js File
This file is required for MSW to function properly in the browser.
Generate it using the following command from the root directory of
your application:
npx msw init <PUBLIC_DIR> --save
This command initializes the MSW service worker and places the mockServiceWorker.js file into the public directory of your React app.
3. Start the Service Worker
Import and start the worker in your application entry point
(typically index.js or App.js).
// src/index.js import React from "react"; import ReactDOM from "react-dom/client"; import "./index.css"; import App from "./App"; import reportWebVitals from "./reportWebVitals"; if (process.env.NODE_ENV === "development") { const mswState = localStorage.getItem("mswState") === "enabled"; if (mswState) { const { worker } = require("./mocks/browser"); worker.start(); window.__mswStop = worker.stop; } } const root = ReactDOM.createRoot(document.getElementById("root")); root.render( <React.StrictMode> <App /> </React.StrictMode> );
4. Verify the Setup
To ensure that the service worker is correctly set up, navigate to
the URL of your application in the browser:
http://localhost:3000/mockServiceWorker.js
You should see the service worker script displayed in your browser. This confirms that the service worker is correctly installed and ready to intercept requests.
If your MSW setup is correct and enabled, you should see a console message indicating that MSW is active. Your browser console should display logs similar to this:
These logs confirm that the MSW service worker is properly intercepting network requests and is ready to mock API responses according to the handlers you’ve defined.
Node Environment Setup
To set up MSW for intercepting requests in a Node.js environment (for example, in server-side tests), follow these steps:
Step 1: Create the server.js File
In the src/mock directory, create a file named server.js. This file sets up the MSW server to intercept requests in a Node.js environment.
// src/mock/server.js import { setupServer } from "msw/browser"; // Create a server instance with the defined request handlers export const server = setupServer();
Step 2: Define the API Handlers
Create a file named posts.js in the handlers directory.This file will describe the APIs you want to intercept and the mock responses.
// src/mock/handlers/posts.js import { http, HttpResponse } from "msw"; export const postHandlers = [ // Handler for GET /todos request http.get("https://jsonplaceholder.typicode.com/todos", () => { return HttpResponse.json([ { id: 1, title: "totam quia non" }, { id: 2, title: "sunt cum tempora" }, ]); }), ];
Here, we're defining that when MSW intercepts a GET request to https://jsonplaceholder.typicode.com/todos, it should respond with a 200 status code and the provided JSON data.
Step 3: Hook Handlers to the Browser Worker
Update the browser.js file to include the defined handlers.
import { setupWorker } from "msw/browser"; import { postHandlers } from "./handlers/posts"; export const worker = setupWorker(...postHandlers);
Step 4: Hook Handlers to the Node Server
Ensure the handlers are also used in the Node.js environment by updating the server.js file.
import { setupServer } from "msw/node"; import { postHandlers } from "./handlers/posts"; export const server = setupServer(...postHandlers);
With these configurations in place, your MSW setup is complete and ready for both browser and Node.js environments. Congratulations on completing the setup! ?
Using MSW in our Tests
To use MSW in your tests, you need to set up your test environment to utilize the mock server for intercepting API calls. Here’s a guide to setting up and writing tests using MSW with your Todos component.
Create the Test File
Create a new file named Todos.MSW.test.js next to your
Todos.jscomponent. This file will contain your tests that
utilize MSW for mocking API responses.
Set Up Test Environment
In your Todos.MSW.test.js file, import the necessary modules and
set up the environment for using MSW with your tests. Below is an
example setup:
import { render, screen, waitFor, within } from "@testing-library/react"; import { http, delay, HttpResponse } from "msw"; import { Todos } from "./Todos"; import { reactQueryWrapper } from "utils/reactQueryWrapper"; import { server } from "mocks/server"; const { wrapper, queryCache } = reactQueryWrapper(); afterEach(() => { queryCache.clear(); }); afterEach(() => { jest.clearAllMocks(); }); beforeAll(() => server.listen()); afterEach(() => server.resetHandlers()); afterAll(() => server.close());
First Test Scenario: Renders loading state
We want to verify that our component correctly displays the loading state while the request is in progress.
test("Renders loading state", () => { server.use( http.get("https://jsonplaceholder.typicode.com/todos", async () => { await delay(1000); return HttpResponse.json([]); }) ); render(<Todos />, { wrapper }); const loadingText = screen.getByText("loading todo list"); expect(loadingText).toBeInTheDocument(); });
Second Test Scenario: Renders error state when request fails or there is network error
We want to verify that the component correctly renders an error state when the API request fails or encounters a network error.
test("Renders error state when request fails or there is network error", async () => { server.use( http.get("https://jsonplaceholder.typicode.com/todos", async () => { return HttpResponse.json([], { status: 500, }); }) ); render(<Todos />, { wrapper }); await screen.findByText("an error occurred fetching todo list"); expect( screen.getByText("an error occurred fetching todo list") ).toBeInTheDocument(); });
Third Test Scenario: Renders list of todos
We want to verify that our component correctly renders the list of todos when the request is successful.
test("Renders list of todos", async () => { render(<Todos />, { wrapper }); const loadingText = screen.queryByText("loading todo list"); await waitFor(() => expect(loadingText).not.toBeInTheDocument()); const list = screen.getByRole("list"); expect(list).toBeInTheDocument(); expect(within(list).getAllByRole("listitem")).toHaveLength(2); });
Fourth Test Scenario: Renders list of todos
We want to verify that your component correctly renders a feedback message when the API request returns an empty list of todos.
test("Renders feedback message when user has an empty list of todos", async () => { server.use( http.get("https://jsonplaceholder.typicode.com/todos", () => { return HttpResponse.json([]); }) ); render(<Todos />, { wrapper }); await waitFor(() => expect(screen.queryByText("loading todo list")).not.toBeInTheDocument() ); expect( screen.getByText("You do not have todos created yet") ).toBeInTheDocument(); });
Final Thoughts
Mocking API calls is crucial for effective testing, allowing you to simulate different scenarios without relying on real backend services. While traditional Jest mocking can be effective, MSW offers a more sophisticated solution with better support for various environments and more realistic request handling.
Here are a few tips to keep in mind:
Choose the Right Tool: Use MSW for a more comprehensive
solution that integrates seamlessly with your React application,
especially when dealing with complex request handling.
Organize Your Handlers: Keep your API handlers well-organized
and modular to make them easier to maintain and extend.
Clean Up After Tests: Ensure that your tests clean up properly
by resetting handlers and clearing mocks to avoid interference
between tests.
Verify Setup: Always check your setup by inspecting the network
requests and responses to ensure that everything is working as
expected.
By incorporating MSW into your testing strategy, you'll achieve more reliable and maintainable tests, leading to a smoother development experience and higher quality software.
Happy testing! ?
위 내용은 모의 네트워크 요청이 쉬워졌습니다: Jest와 MSW 통합의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!