编写涉及模拟或存根 API 调用的单元测试可能会让人感到不知所措——我自己也经历过这种情况。在本文中,我将指导您通过一种更简单、更有效的方法在测试期间模拟 API 请求。
在我们深入之前,这里是我们将使用的工具列表:
如果您对所有内容都不熟悉,请不要担心 - 只需按照说明进行操作,我将引导您完成每个步骤。
这是该项目的 Github 链接
概述
我们将构建一个简单的 React 应用程序,该应用程序使用 Json Placeholder API 获取并显示 Todos 列表。在此项目中,我们将编写测试场景来验证以下内容:
本指南将涵盖两种主要方法;
让我们开始吧!
要开始构建应用程序,请按照以下步骤操作:
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 组件并编写我们的测试..
我们有一个名为 getTodos 的函数,它负责进行 API 调用来检索待办事项列表并返回响应。
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 组件及其子组件可以使用 React-query 来管理测试中的服务器状态。
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中文网其他相关文章!