”工欲善其事,必先利其器。“—孔子《论语.录灵公》
首页 > 编程 > 模拟网络请求变得容易:集成 Jest 和 MSW

模拟网络请求变得容易:集成 Jest 和 MSW

发布于2024-11-02
浏览:982

Writing unit tests that involve mocking or stubbing API calls can feel overwhelming—I’ve been there myself. In this article, I’ll guide you through a simpler, more efficient way to mock API requests during testing.
Before we dive in, here’s a list of tools we’ll be using:

  • React
  • React Testing Library
  • Jest
  • Craco : > we will need this to extend jest config later on
  • MSW (mock service worker)

Don’t worry if you’re not familiar with everything—just follow along, and I’ll walk you through each step.

Here is the Github link to the project

Overview
We’ll be building a simple React app that fetches and displays a list of Todos using the Json Placeholder API. In this project, we’ll write test scenarios to verify the following:

  • The app shows a loading state while the API request is in progress.
  • It displays an error message if the request fails.
  • It ensures the list of data from the API is correctly rendered.
  • It provides feedback when the request is successful but returns no data.

This guide will cover two main approaches;

  1. The Traditional Way of Mocking API Calls
  2. The Modern Approach Using MSW (Mock Service Worker)

Let's get started!

To start building the app, follow these steps:

1. Create a new React app: Run the following command to create your React application:

npx create-react-app msw-example

2. Start the application: After the setup, navigate to the project folder and run:

npm start

3. Install necessary packages: Next, install @tanstack/react-query to manage client-side data:

npm install @tanstack/react-query

React Query (Tanstack Query) helps in handling server-side state management, including caching, synchronization, and data fetching. You can learn more about it in their Official Documentation

You can now start writing the app logic and set up React Query to manage data fetching efficiently. Here is what it looks like after setting it up.

import { QueryClient, QueryClientProvider } from "@tanstack/react-query";

import { Todos } from "components/Todos";

const queryClient = new QueryClient();

function App() {
  return (
    
      
); } export default App;

Next we create the Todos component rendering a list of our 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 

loading todo list

; } if (isError) { return

an error occurred fetching todo list

; } return (
{Boolean(data.length) ? (
    {data.map((item) => (
  1. {item.title}
  2. ))}
) : (

You do not have todos created yet

)}
); }

Now that everything is set up, let’s start writing the tests for the scenarios we outlined earlier. First, we’ll implement this using the traditional approach that we’re already familiar with—mocking API calls using Jest

Checkout the github repo so you can follow along

The Traditional Way of Mocking API Calls

React and Jest work seamlessly together out of the box, so there’s no need for additional configuration or setup—at least for now. We’ll create a file named Todos.test.js next to our Todo.js component, where we’ll import the Todos component and write our tests..

We have a function called getTodos which is responsible for making an API call to retrieve the list of todos and returning the response.

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();
}


In your Todos.test.js file, you need to import the Todos component and create a utility function that provides a wrapper with the React Query provider. This ensures that the Todos component and its children can use react-query for managing server state in tests.

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();

Next, we need to mock the getTodos function. This will allow us to specify the return values for each test scenario, giving us control over the data the function returns during testing. Additionally, we’ll ensure that any leftover data from previous test cases is cleaned up, so each test case starts with a clean slate.

Code Sample

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();
});

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", () => {
  getTodos.mockImplementation(() => {
    return new Promise((resolve) => {
      setTimeout(() => {
        resolve();
      }, 1000);
    });
  });

  render(, { 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 () => {
  getTodos.mockImplementationOnce(() => {
    return new Promise((resolve, reject) => {
      reject();
    });
  });

  render(, { 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 () => {
  getTodos.mockImplementation(() => {
    return Promise.resolve([
      { id: 1, title: "Exercise" },
      { id: 2, title: "Cook" },
    ]);
  });

  render(, { 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 () => {
  getTodos.mockImplementationOnce(() => {
    return Promise.resolve([]);
  });

  render(, { 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  --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(
  
    
  
);

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:

Mocking Network Requests Made Easy: Integrating Jest and MSW

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.

  1. 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.

  2. 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(, { 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(, { 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(, { 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(, { 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! ?

版本声明 本文转载于:https://dev.to/rolandawoda/mocking-network-requests-made-easy-integrating-jest-and-msw-58ak?1如有侵犯,请联系[email protected]删除
最新教程 更多>
  • 您如何在Laravel Blade模板中定义变量?
    您如何在Laravel Blade模板中定义变量?
    在Laravel Blade模板中使用Elegance 在blade模板中如何分配变量对于存储以后使用的数据至关重要。在使用“ {{}}”分配变量的同时,它可能并不总是最优雅的解决方案。幸运的是,Blade通过@php Directive提供了更优雅的方法: $ old_section =“...
    编程 发布于2025-07-16
  • 为什么尽管有效代码,为什么在PHP中捕获输入?
    为什么尽管有效代码,为什么在PHP中捕获输入?
    在php ;?>" method="post">The intention is to capture the input from the text box and display it when the submit button is clicked.但是,输出...
    编程 发布于2025-07-16
  • Java字符串非空且非null的有效检查方法
    Java字符串非空且非null的有效检查方法
    检查字符串是否不是null而不是空的 if(str!= null && str.isementy())二手: if(str!= null && str.length()== 0) option 3:trim()。isement(Isement() trim whitespace whitesp...
    编程 发布于2025-07-16
  • 如何高效地在一个事务中插入数据到多个MySQL表?
    如何高效地在一个事务中插入数据到多个MySQL表?
    mySQL插入到多个表中,该数据可能会产生意外的结果。虽然似乎有多个查询可以解决问题,但将从用户表的自动信息ID与配置文件表的手动用户ID相关联提出了挑战。使用Transactions和last_insert_id() 插入用户(用户名,密码)值('test','test...
    编程 发布于2025-07-16
  • 如何在鼠标单击时编程选择DIV中的所有文本?
    如何在鼠标单击时编程选择DIV中的所有文本?
    在鼠标上选择div文本单击带有文本内容,用户如何使用单个鼠标单击单击div中的整个文本?这允许用户轻松拖放所选的文本或直接复制它。 在单个鼠标上单击的div元素中选择文本,您可以使用以下Javascript函数: function selecttext(canduterid){ if(do...
    编程 发布于2025-07-16
  • CSS强类型语言解析
    CSS强类型语言解析
    您可以通过其强度或弱输入的方式对编程语言进行分类的方式之一。在这里,“键入”意味着是否在编译时已知变量。一个例子是一个场景,将整数(1)添加到包含整数(“ 1”)的字符串: result = 1 "1";包含整数的字符串可能是由带有许多运动部件的复杂逻辑套件无意间生成的。它也可以是故意从单个真理...
    编程 发布于2025-07-16
  • 如何使用PHP将斑点(图像)正确插入MySQL?
    如何使用PHP将斑点(图像)正确插入MySQL?
    essue VALUES('$this->image_id','file_get_contents($tmp_image)')";This code builds a string in PHP, but the function call ...
    编程 发布于2025-07-16
  • PHP阵列键值异常:了解07和08的好奇情况
    PHP阵列键值异常:了解07和08的好奇情况
    PHP数组键值问题,使用07&08 在给定数月的数组中,键值07和08呈现令人困惑的行为时,就会出现一个不寻常的问题。运行print_r($月份)返回意外结果:键“ 07”丢失,而键“ 08”分配给了9月的值。此问题源于PHP对领先零的解释。当一个数字带有0(例如07或08)的前缀时,PHP将...
    编程 发布于2025-07-16
  • 如何从Google API中检索最新的jQuery库?
    如何从Google API中检索最新的jQuery库?
    从Google APIS 问题中提供的jQuery URL是版本1.2.6。对于检索最新版本,以前有一种使用特定版本编号的替代方法,它是使用以下语法:获取最新版本:未压缩)While these legacy URLs still remain in use, it is recommended ...
    编程 发布于2025-07-16
  • 如何使用Depimal.parse()中的指数表示法中的数字?
    如何使用Depimal.parse()中的指数表示法中的数字?
    在尝试使用Decimal.parse(“ 1.2345e-02”中的指数符号表示法表示的字符串时,您可能会遇到错误。这是因为默认解析方法无法识别指数符号。 成功解析这样的字符串,您需要明确指定它代表浮点数。您可以使用numbersTyles.Float样式进行此操作,如下所示:[&& && && ...
    编程 发布于2025-07-16
  • 为什么使用固定定位时,为什么具有100%网格板柱的网格超越身体?
    为什么使用固定定位时,为什么具有100%网格板柱的网格超越身体?
    网格超过身体,用100%grid-template-columns 为什么在grid-template-colms中具有100%的显示器,当位置设置为设置的位置时,grid-template-colly修复了?问题: 考虑以下CSS和html: class =“ snippet-code”> g...
    编程 发布于2025-07-16
  • 大批
    大批
    [2 数组是对象,因此它们在JS中也具有方法。 切片(开始):在新数组中提取部分数组,而无需突变原始数组。 令ARR = ['a','b','c','d','e']; // USECASE:提取直到索引作...
    编程 发布于2025-07-16
  • 在Ubuntu/linux上安装mysql-python时,如何修复\“ mysql_config \”错误?
    在Ubuntu/linux上安装mysql-python时,如何修复\“ mysql_config \”错误?
    mysql-python安装错误:“ mysql_config找不到”“ 由于缺少MySQL开发库而出现此错误。解决此问题,建议在Ubuntu上使用该分发的存储库。使用以下命令安装Python-MysqldB: sudo apt-get安装python-mysqldb sudo pip in...
    编程 发布于2025-07-16
  • 在GO中构造SQL查询时,如何安全地加入文本和值?
    在GO中构造SQL查询时,如何安全地加入文本和值?
    在go中构造文本sql查询时,在go sql queries 中,在使用conting and contement和contement consem per时,尤其是在使用integer per当per当per时,per per per当per. 在GO中实现这一目标的惯用方法是使用fmt.spr...
    编程 发布于2025-07-16
  • 如何在Java字符串中有效替换多个子字符串?
    如何在Java字符串中有效替换多个子字符串?
    在java 中有效地替换多个substring,需要在需要替换一个字符串中的多个substring的情况下,很容易求助于重复应用字符串的刺激力量。 However, this can be inefficient for large strings or when working with nu...
    编程 发布于2025-07-16

免责声明: 提供的所有资源部分来自互联网,如果有侵犯您的版权或其他权益,请说明详细缘由并提供版权或权益证明然后发到邮箱:[email protected] 我们会第一时间内为您处理。

Copyright© 2022 湘ICP备2022001581号-3