为了账号安全,请及时绑定邮箱和手机立即绑定

React TypeScript项目中的有效测试策略:使用Jest和API模拟

本文将讨论为什么我们真正需要一个测试策略,如何利用MSW来进行API模拟测试,以及设置MSW以适应React TypeScript项目所需的事项。

高层设计

测试是任何软件开发生命周期中至关重要的一部分,确保您的应用程序按预期工作且无缺陷。对于 React TypeScript 项目,利用 Jest 和 MSW(Mock Service Worker)这样的工具来模拟 API 调用可以简化测试流程并增强代码的健壮性。

如果你正在为你的 React TypeScript 项目寻找 Jest 设置,请参阅下面的参考信息。

现代测试环境:用于 React TypeScript 项目的 Jest 和 React 测试库 详细说明 React 应用程序单个测试用例的设置和执行流程,强调等…levelup.gitconnected.com
为什么需要测试策略呢?

质量

每个组织都谈论代码质量,但我们如何衡量它呢?对于代码质量的期望,我们通常考虑零代码异味(code smell)、零漏洞、零bug,并且代码覆盖率超过80%。此外,我们还需要了解这些工具是如何计算这些值的,以及这些工具的计算原理。

比如使用带有 collectCoverage 选项的 Jest 工具,可以跟踪测试时运行的代码量。

但是如果我们没有合适的方法或策略,我们可以寻找替代方案,这会导致生产中出现更多的bug,即使代码覆盖率达到了80%以上。

在测试用TypeScript编写的React组件时,我们需要考虑以下几个因素。

  1. 使用 React 测试库 (RTL):RTL(React 测试库)提供了一种简单直接的方法来测试 React 组件。它鼓励测试组件的行为和用户交互,而非内部状态。
  2. 利用类型安全性:TypeScript 提供的类型安全性可以帮助提高测试的可维护性。TypeScript 可以检测组件 props 或状态的任何更改,并在测试代码中突出显示编译器错误,以相应地更新测试。
  3. 测试自定义 Hooks:虽然通常不需要单独测试 Hooks,但对于复杂或可重用的 Hooks,可以利用诸如 react-hooks-testing-library 等库独立于任何组件进行测试。
  4. 避免模拟组件级别的实现:可以使用像 MSW 这样的工具在网络级别拦截网络请求,可以在不模拟组件实现的情况下模拟 API 的响应。
什么是测试层次结构呢?

拥有充分的单元测试和集成测试覆盖率是坚实的基础,但这还不够,通过加入有针对性的端到端测试并采用平衡的测试策略,这将有助于进一步提升应用程序的整体质量和可靠性。定期审查并调整测试方法,以适应代码库的变化和扩展。

测试三角形

这里有一个简化的测试金字塔版本,该金字塔来自2014年Google测试自动化大会开幕式上的演讲。

快速行动,不要把事情搞砸了。

我认为在上述提到的所有层次写测试是一种持续的贡献行为,应该包含在我们对功能开发的定义里。

关于80%代码覆盖率的误解:常见的坑

在审查测试用例报告时,我们关注代码覆盖率,并根据这个来评估代码质量。但是结果却出乎我们的预料。这个过程实际上违背了编写这些测试用例的初衷。

为什么在这个场景下编写相关的测试用例很重要?Kent C. Dodds: 解释了为什么以及怎样编写测试用例。

写些测试。但也不要太多。主要是集成层面的测试。

让我们来看看几个涉及React TypeScript测试的情况,这些情况给我们提供了代码覆盖率方面的错误数据。

  1. 测试 Getter 和 Setter — 编写仅覆盖简单的 Getter 和 Setter 方法的测试,这只会虚增覆盖率指标而没有提供有意义的测试。
  • 类的实现方式
    class 产品 {  
      构造函数(价格) {  
        this._价格 = 价格;  
      }  

      获取价格() {  
        return this._价格;  
      }  

      设置价格(newPrice) {  
        this._价格 = newPrice;  
      }  

      应用折扣(折扣) {  
        this._价格 -= 折扣;  
      }  
    }  

    导出默认产品
  • 基本测试(虚高覆盖率)
    import Product from './Product';  

    test('获取价格应为', () => {  
      const product = new Product(100);  
      expect(product.price).toBe(100);  
    });  

    test('设置价格应为', () => {  
      const product = new Product(100);  
      product.price = 150;  
      expect(product.price).toBe(150);  
    });
  • 增加有意义的行为
    import Product from './Product';  

    test('应用折扣功能正确地减少价格', () => {  
      const product = new Product(100);  
      product.applyDiscount(20);  
      expect(product.price).toBe(80);  
    });

2. 无断言行为 — 包括调用函数却不检查预期结果和副作用的测试用例。

    import Product from './Product';  

    test('正确应用折扣以降低价格', () => {  
      const product = new Product(100);  
      product.applyDiscount(20);  
    });

3. 测试实现细节 — 编写侧重于内部实现细节而非公共API或用户可见的行为的测试,导致即使功能已损坏,测试仍然可以通过。

  • 组件的实现
    mport React, { useState } from 'react';  

    const TodoList = () => {  
      const [todos, setTodos] = useState([]);  

      const addTodo = (todo) => {  
        setTodos([...todos, todo]);  
      };  

      const removeTodo = (index) => {  
        const updatedTodos = [...todos];  
        updatedTodos.splice(index, 1);  
        setTodos(updatedTodos);  
      };  

      return (  
        <div>  
          <button onClick={() => addTodo('New Todo')}>新增事项</button>  
          <ul>  
            {todos.map((todo, index) => (  
              <li key={index}>  
                {todo} <button onClick={() => removeTodo(index)}>删除</button>  
              </li>  
            ))}  
          </ul>  
        </div>  
      );  
    };  

    export default TodoList;
  • 注重细节的实现测试
    import React from 'react';  
    import { render, screen } from '@testing-library/react';  
    import TodoList from './TodoList';  

    test('添加一个待办事项并检查列表状态', () => {  
      render(<TodoList />);  
      const button = screen.getByText('新增待办事项');  
      button.click();  

      const todoList = screen.getByRole('list');  
      expect(todoList.children.length).等于(1);   
    });

更侧重于用户行为的测试

    import React from 'react';  
    import { render, screen } from '@testing-library/react';  
    import TodoList from './TodoList';  

    test('添加一条待办事项并显示它', () => {  
      render(<TodoList />);  
      const button = screen.getByText('添加待办事项');  
      button.click();  

      expect(screen.getByText('新待办事项')).toBeInTheDocument();  
    });  

    test('从列表中删除一个待办事项', () => {  
      render(<TodoList />);  
      const button = screen.getByText('添加待办事项');  
      button.click();  
      const removeButton = screen.getByText('删除');  
      removeButton.click();  

      expect(screen.queryByText('新待办事项')).not.toBeInTheDocument();   
    });

4. 未测试边界条件 — 没有涵盖边界条件、错误处理的测试、异常输入的测试,导致高覆盖率并不能真实反映代码的健壮性

  • 组件实现
import React from 'react';  

const Divider = ({ numerator, denominator }) => {  
  if (denominator === 0) return <p>除数不能为零</p>;  
  return <p>{numerator / denominator}</p>;  
};  

export default Divider;
  • 基础测试,不测试特殊情况
    import React from 'react';  
    import { render, screen } from '@testing-library/react';  
    import Divider from './Divider';  

    测试('将两个数字相除', () => {  
      render(<Divider numerator={10} denominator={2} />);  
      预期(screen.getByText('5')).在文档中存在();  
    });  

    测试('将另一对数字相除', () => {  
      render(<Divider numerator={20} denominator={4} />);  
      预期(screen.getByText('5')).在文档中存在();  
    });
  • 改进的测试,包括极端情况
    import React from 'react';  
    import { render, screen } from '@testing-library/react';  
    import Divider from './Divider';  

    test('能够正确除法运算', () => {  
      render(<Divider numerator={10} denominator={2} />);  
      expect(screen.getByText('5')).toBeInTheDocument();  
    });  

    test('尝试除以零时返回错误信息', () => {  
      render(<Divider numerator={10} denominator={0} />);  
      expect(screen.getByText('除以零错误')).toBeInTheDocument();  
    });  

    test('能够处理负数的除法运算', () => {  
      render(<Divider numerator={-10} denominator={2} />);  
      expect(screen.getByText('-5')).toBeInTheDocument();  
    });

5. 这样的测试,通过过度嘲讽依赖性,掩盖了组件间集成可能存在的问题。

  • 组件的实现
    import React, { useEffect, useState } from 'react';  

    const UserProfile = ({ api }) => {  
      const [user, setUser] = useState(null);  

      useEffect(() => {  
        const fetchUser = async () => {  
          const data = await api.getUser();  
          setUser(data);  
        };  
        fetchUser();  
      }, [api]);  

      if (!user) return <div>正在加载...</div>;  

      return <div>{user.name}</div>;  
    };  

    export default UserProfile;
  • 过度嘲讽的表面测试
    import React from 'react';  
    import { render, screen } from '@testing-library/react';  
    import UserProfile from './UserProfile';  

    const mockApi = {  
      getUser: jest.fn().mockResolvedValue({ name: 'Santosh' }), // 模拟获取用户信息的函数
    };  

    test('显示用户资料', async () => { // 测试用户资料的渲染功能
      render(<UserProfile api={mockApi} />);  

      // 预期页面正在加载中...
      expect(screen.getByText('正在加载...')).toBeInTheDocument();  
      // 等待页面加载出用户名字'Santosh'
      await screen.findByText('Santosh');   
    });
  • 更真实的集成测试
import React from 'react';  
import { render, screen } from '@testing-library/react';  
import UserProfile from './UserProfile';  

const mockApi = {  
  getUser: jest.fn().mockResolvedValue({ name: 'Santosh' }),  
};  

test('测试用户配置文件的正确集成', async () => {  
  render(<UserProfile api={mockApi} />);  

  expect(screen.getByText('加载中...')).toBeInTheDocument();  
  const userName = await screen.findByText('Santosh');  

  expect(screen.getByText('Santosh')).toBeInTheDocument();  
  expect(mockApi.getUser).toHaveBeenCalledTimes(1);   
});

6. 忽略异步代码的测试 — 忽视了正确测试异步代码路径,这使得通过的测试实际上未验证异步行为

  • 组件的实现
    import React, { useEffect, useState } from 'react';  
    import { getUser } from './api';  

    const 用户资料 = ({ api }) => {  
      const [user, setUser] = useState(null);  

      useEffect(() => {  
        const fetchUser = async () => {  
          const data = await getUser();  
          setUser(data);  
        };  
        fetchUser();  
      }, []);  

      if (!user) return <div>正在加载...</div>;  

      return <div>{user.name}</div>;  
    };  

    export default 用户资料;
  • 测试异步代码
    import React from 'react';  
    import { render, screen } from '@testing-library/react';  
    import UserProfile from './UserProfile';  
    import { getUser } from './api';  

    jest.mock('./api', () => ({  
        getUser: () => jest.fn().mockResolvedValue({ name: 'Santosh' })  
    })); // 模拟从./api导入的getUser函数,使其返回一个名为Santosh的解析值

    test('测试用户资料渲染', () => {  
      render(<UserProfile />);  

      expect(screen.getByText('加载中...')).toBeInTheDocument(); // 检查是否显示“加载中...”文本  
      expect(screen.getByText('Santosh')).toBeInTheDocument(); // 检查是否显示“Santosh”文本   
    });
  • 改进了异步代码的测试处理 — MSW 是一个强大的工具,用于在测试和开发中模拟 API。
    import { setupServer } from 'msw/node';  
    import { http, HttpResponse } from 'msw'  
    import { handlers } from './handlers';  

    const handlers = [  
     http.get('/api/user', ({ request, params, cookies }) => {  
        return HttpResponse.json({ name: 'Santosh', age: 30 });  
    } ]  
    export const server = setupServer(...handlers);  

    beforeAll(() => server.listen());  
    afterEach(() => server.resetHandlers());  
    afterAll(() => server.close());  

    test('测试用户资料页面的渲染', () => {  
      render(<UserProfile />);  

      expect(screen.getByText('加载中...')).toBeInTheDocument();  
      expect(screen.getByText('Santosh')).toBeInTheDocument();   
    });

7. 重数量轻质量,轻视质量:优先考虑测试的数量,而非测试的质量和实际意义,导致了大量的表面文章的测试。

8. 过于简单的测试数据 — 使用过于简单的测试数据,无法代表真实的使用场景,导致测试无法真正验证代码在实际情况中的表现。

入门指南 — MSW(模拟服务工作者)

在我们的情况下,我们将使用MSW 2.0,它在2023年10月23日发布。如果您想要查看迁移指南,可以在这里找到它。我只会添加我在项目中用到的设置部分。

他们的官方文档详细解释了他们提供的各种功能。

MSW 可以让你不更改源代码模拟网络调用。

这真的非常有帮助,让你可以无缝测试代码,并创建一个基于实际生产的场景来调用API接口。

MSW 是什么?(MSW 指的是什么?)

MSW 是一个适用于浏览器和 Node.js 的流畅 REST/GraphQL API 模拟库(mocking library)。

MSW

[使用 Mock Service Worker 模拟 REST 和 GraphQL API 介绍

大家好,我是 Artem。在这门课程里,我会教你如何使用 Mock Service Worker 模拟 REST 和 GraphQL 接口……egghead.io](https://egghead.io/lessons/javascript-introduction-to-mock-rest-and-graphql-apis-with-mock-service-worker?source=post_page-----ef768849e26e--------------------------------)

  1. 在你的项目里安装MSW组件

在项目中安装msw作为开发依赖:

     yarn add msw --dev

2. 创建模拟服务器设置

  • 创建一个 server.ts 文件,其中包含的配置用于使用 MSW 创建一个模拟服务器。
  • 您可以将此文件放在任何位置(例如项目根目录),但为了启动模拟服务器,我们需要调用 mockServer 方法。我们可以使用默认处理程序,并根据实际情况添加额外的处理程序。
    // mock/server.ts
    import { RequestHandler } from "msw";
    import { setupServer } from "msw/node";

    // 这会配置一个使用给定请求处理程序的请求模拟服务器。
    const mockServer = (handlers: RequestHandler[] = []) => {
      const server = setupServer(...handlers);

      在所有测试前(() => 
        server.listen({
          未处理请求时: "error",
        }),
      );
      在每个测试后(() => server.listHandlers());
      在所有测试后(() => server.close());

      return server;
    };

    export { mockServer };

3. 为 Node.js 创建适用于 JEST 环境的 polyfills 配置

  • 在使用 MSW 和 Jest 进行 Node.js 测试时,经常会遇到一些问题,为了解决这些问题,我们需要使用 polyfills。
  • 例如 —— [请求](https://mswjs.io/docs/migrations/1.x-to-2.x#requestresponsetextencoder-is-not-defined-jest)/[响应](https://mswjs.io/docs/migrations/1.x-to-2.x#requestresponsetextencoder-is-not-defined-jest)/[TextEncoder](https://mswjs.io/docs/migrations/1.x-to-2.x#requestresponsetextencoder-is-not-defined-jest) 未定义 (Jest)
  • 在根目录下创建 jest.polyfills.ts 文件,并在 jest.config.ts 中添加相应的配置。
  • 在你的项目中安装 [u](https://mswjs.io/docs/migrations/1.x-to-2.x#requestresponsetextencoder-is-not-defined-jest)ndici`

[u](https://mswjs.io/docs/migrations/1.x-to-2.x#requestresponsetextencoder-is-not-defined-jest)ndici

    // jest.polyfills.ts  
    import { Blob } from 'node:buffer';  
    import { ReadableStream, TransformStream } from 'node:stream/web';  
    import { TextDecoder, TextEncoder } from 'node:util';  

    Object.defineProperties(globalThis, {  
        TextDecoder: { value: TextDecoder },  
        TextEncoder: { value: TextEncoder },  
        ReadableStream: { value: ReadableStream },  
        TransformStream: { value: TransformStream },  
    });  

    import { fetch, FormData, Headers, Request, Response } from 'undici';  

    Object.defineProperties(globalThis, {  
        fetch: { value: fetch, writable: true },  
        Blob: { value: Blob },  
        File: { value: File },  
        Headers: { value: Headers },  
        FormData: { value: FormData },  
        Request: { value: Request },  
        Response: { value: Response },  
    });

4. 更新每个环境特有的jest设置

  • 在 jest 配置中加入 polyfills 配置项 - 用于配置或设置测试环境的模块路径,这些模块会在每个测试运行前执行一些代码。
  • 找到模块 ‘msw/node’ (JSDOM) 时会抛出此错误 - 这个错误是由测试运行器抛出的,因为 JSDOM 默认使用 browser 导出条件。这意味着当你导入像 MSW 这样的第三方包时,JSDOM 会强制使用其 browser 导出作为入口点。这是不正确的且可能带来危险,因为也就是说,JSDOM 实际上是在 Node.js 环境下运行,设计上无法保证完全的浏览器兼容性。
    // 传递给测试环境的选项如下  
    testEnvironmentOptions:,  
    {  
        customExportConditions: [''],  
    },  
    // 配置测试环境的文件路径  
    setupFiles: [  
        '<rootDir>/jest.polyfills.ts'  
    ],  

    // 配置测试框架的文件路径  
    setupFilesAfterEnv: [  
        '<rootDir>/jest.setup.ts'  
    ],
  • Jest 配置,用于引用
    // jest.config.ts  
    /**  

* 每个配置属性的详细说明,请访问:  

* https://jestjs.io/docs/configuration  
     */  

    import type {Config} from 'jest';  

    const config: Config = {  
      // 所有导入模块应自动进行模拟  
      // automock: false,  

      // 在测试失败 `n` 次后停止运行  
      // bail: 0,  

      // Jest 应该将缓存依赖项信息存储在哪个目录  
      // cacheDirectory: "/private/var/folders/ll/2btx3mzx3cv207gm_x_l9kfc0000gn/T/jest_dx",  

      // 在每次测试前自动清除模拟调用、实例、上下文和结果  
      clearMocks: true,  

      // 指定是否在执行测试时收集覆盖率信息  
      collectCoverage: true,  

      // 指定一组文件的 glob 模式,用于在测试时收集覆盖率信息  
      // collectCoverageFrom: undefined,  

      // Jest 应该将覆盖率文件输出到哪个目录  
      coverageDirectory: "coverage",  

      // 一个正则表达式模式字符串数组,用于匹配在考虑模块为“可见”前的模块路径  
      // coveragePathIgnorePatterns: [  
      //   "/node_modules/"  
      // ],  

      // 指定应使用哪个提供者来为覆盖率收集工具代码  
      coverageProvider: "v8",  

      // 一个报告器名称的列表,Jest 用于编写覆盖率报告  
      // coverageReporters: [  
      //   "json",  
      //   "text",  
      //   "lcov",  
      //   "clover"  
      // ],  

      // 一个配置最小阈值强制执行覆盖率结果的对象  
      // coverageThreshold: undefined,  

      // 一个路径,指向一个自定义依赖项提取器模块  
      // dependencyExtractor: undefined,  

      // 调用已弃用的 API 时,Jest 应该抛出有用的错误信息  
      // errorOnDeprecated: false,  

      // 模拟时钟的默认配置  
      // fakeTimers: {  
      //   "enableGlobally": false  
      // },  

      // 强制从忽略的文件中收集覆盖率信息  
      // forceCoverageMatch: [],  

      // 一个路径,指向一个模块,该模块导出一个在所有测试套件之前触发一次的异步函数  
      // globalSetup: undefined,  

      // 一个路径,指向一个模块,该模块导出一个在所有测试套件之后触发一次的异步函数  
      // globalTeardown: undefined,  

      // 一个包含需要在所有测试环境中可用的全局变量的集合  
      // globals: {},  

      // 最多使用的工作者数量,可以指定为百分比或数字。例如 maxWorkers: 10% 将使用 CPU 数量的 10% + 1 作为最大工作者数。maxWorkers: 2 将使用最多 2 个工作者。  
      // maxWorkers: "50%",  

      // 从要求模块位置递归搜索的目录数组  
      // moduleDirectories: [  
      //   "node_modules"  
      // ],  

      // 模块使用的文件扩展名数组  
      moduleFileExtensions: [  
        "js",  
        "mjs",  
        "cjs",  
        "jsx",  
        "ts",  
        "tsx",  
        "json",  
        "node"  
      ],  

      // 一个正则表达式映射到模块名或模块名数组,允许使用单个模块来模拟资源  
      moduleNameMapper: {  
        // 模拟资产和样式  
        '^.+\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)

  Jest 引用设置文件
导入 '@testing-library/jest-dom'

5\. **编写API处理器**

* 在你的项目中基于你的选择创建一个处理器文件夹,但最好为每个实体类型(如用户)单独创建一个文件,并在该文件中单独处理成功和失败情况。
* 下面是一个我们如何处理获取用户 API 响应的过程的示例,包括成功和失败消息。
// mock/handlers/users.ts  
import { http, HttpResponse } from "msw";  

const host = 'http://localhost:8080';  

const user = {  
  name: "santosh shinde",  
  blog: "http://blog.santoshshinde.com/",  
  linkedin: "https://www.linkedin.com/in/santosh-shinde-54454635/",  
};  

// 成功的响应  
const getUserSuccess = http.get(`${host}/api/user`, () => {  
  return HttpResponse.json(user);  
});  

// 错误的响应  
const getUserError = http.get(`${host}/api/user`, () => {  
  return new HttpResponse(null, { status: 500 });  // 返回500状态码的错误响应  
});  

export { getUserSuccess, getUserError };

6\. **写测试用例**

* 可以在项目中实现的功能需求和API请求的基础上创建测试用例。这里有一个例子来说明,我们在模拟用户端点接口时尝试同时模拟成功和失败的场景。
import React from "react";  
import { Provider } from "react-redux";  

import { render, screen, waitFor } from "@testing-library/react";  

import App from "./App";  
import { store } from "./store";  
import { getUserError, getUserSuccess, user } from "../mock/handlers/users";  
import { mockServer } from "../mock/server";  

describe("App", () => {  
  // 创建模拟服务器实例  
  const server = mockServer();  

  beforeEach(() => {  
    // 在每次测试前重置处理程序  
    server.resetHandlers();  
  });  

  it("显示带有数据的成功状态", async () => {  
    // 使用msw来模拟成功获取用户数据的API调用  
    server.use(getUserSuccess);  
    render(  
      <Provider store={store}>  
        <App />  
      </Provider>,  
    );  
    await waitFor(() => {  
      const linkElement = screen.getByText(user.name);  
      expect(linkElement).toBeInTheDocument();  
    });  
  });  

  it("显示错误信息", async () => {  
    // 使用msw来模拟获取用户数据失败的API调用  
    server.use(getUserError);  
    render(  
      <Provider store={store}>  
        <App />  
      </Provider>,  
    );  
    await waitFor(() => {  
      const linkElement = screen.getByText('Error');  
      expect(linkElement).toBeInTheDocument();  
    });  
  });  
});

如果你想找到一个完整的例子,请查看下方的ReactJS应用仓库,我会不断更新它,使其保持与最新标准和代码库同步。

## [GitHub - santoshshinde2012/speedyreact-kit: 一个现代、高效的 React Redux Esbuild Jest 启动模板,用于启动 React 项目——github.com](https://github.com/santoshshinde2012/speedyreact-kit?source=post_page-----ef768849e26e--------------------------------)

感谢阅读。请在下面留言评论,如果这篇博客对您有帮助,请点个赞。谢谢!

请关注我**_Twitter_** ([https://twitter.com/shindesan2012](https://twitter.com/shindesan2012)) 和 **_LinkedIn_** ([https://www.linkedin.com/in/shindesantosh/](https://www.linkedin.com/in/shindesantosh/)),并通过[**_买我一杯咖啡_**](https://www.buymeacoffee.com/santoshshin)支持我,来支持我的创作。
:  
          '<rootDir>/mock/file.ts',  
        '^.+\\.(css|less|scss|sass)

* Jest Setup file for references
import '@testing-library/jest-dom'

5\. **Write API handlers**

* Create a handlers folder in your project based on your choice, but it will be good if you can separate a file for each entity, such as users, and handle its success and error cases in that file only.

* Please find below an example of how we handle the process of retrieving user API responses, including both success and error messages.
// mock/handlers/users.ts  
import { http, HttpResponse } from "msw";  

const host = 'http://localhost:8080';  

const user = {  
  name: "santosh shinde",  
  blog: "http://blog.santoshshinde.com/",  
  linkedin: "https://www.linkedin.com/in/santosh-shinde-54454635/",  
};  

// for success response  
const getUserSuccess = http.get(`${host}/api/user`, () => {  
  return HttpResponse.json(user);  
});  

// for error response  
const getUserError = http.get(`${host}/api/user`, () => {  
  return new HttpResponse(null, { status: 500 });  
});  

export { getUserSuccess, getUserError };

6\. **Write Test cases**

* You can create test cases based on the use cases and API calls you are implementing in your project. Here is an example where we have a user endpoint and are attempting to simulate both success and error states.
import React from "react";  
import { Provider } from "react-redux";  

import { render, screen, waitFor } from "@testing-library/react";  

import App from "./App";  
import { store } from "./store";  
import { getUserError, getUserSuccess, user } from "../mock/handlers/users";  
import { mockServer } from "../mock/server";  

describe("App", () => {  
  // create mock server instance  
  const server = mockServer();  

  beforeEach(() => {  
    // reset handlers  
    server.resetHandlers();  
  });  

  it("renders success state with data", async () => {  
    // mock success api call using msw  
    server.use(getUserSuccess);  
    render(  
      <Provider store={store}>  
        <App />  
      </Provider>,  
    );  
    await waitFor(() => {  
      const linkElement = screen.getByText(user.name);  
      expect(linkElement).toBeInTheDocument();  
    });  
  });  

  it("renders error state", async () => {  
    // mock error api call using msw  
    server.use(getUserError);  
    render(  
      <Provider store={store}>  
        <App />  
      </Provider>,  
    );  
    await waitFor(() => {  
      const linkElement = screen.getByText('Error');  
      expect(linkElement).toBeInTheDocument();  
    });  
  });  
});

If you are looking for a full example, please find below the ReactJS application repository, which I am continuously upgrading to sync with the latest standards and code base.

## [GitHub - santoshshinde2012/speedyreact-kit: The React Redux Esbuild Jest Starter is a modern…The React Redux Esbuild Jest Starter is a modern, efficient boilerplate designed to kickstart your React projects with…github.com](https://github.com/santoshshinde2012/speedyreact-kit?source=post_page-----ef768849e26e--------------------------------)

Thanks for reading. Please do share your comments and give a clap if this blog has added value to your learning.

_Follow me on_[** _Twitter_**](https://twitter.com/shindesan2012) _and_[** _LinkedIn_**](https://www.linkedin.com/in/shindesantosh/)** __** and support my work by [**_buying me a coffee._**](https://www.buymeacoffee.com/santoshshin)
: '<rootDir>/mock/style.ts'  
      },  

      // 一个正则表达式模式字符串数组,匹配模块路径前考虑模块为“可见”  
      // modulePathIgnorePatterns: [],  

      // 启用测试结果通知  
      // notify: false,  

      // 一个枚举,指定通知模式(需启用 { notify: true })  
      // notifyMode: "failure-change",  

      // 用作 Jest 配置基础的预设  
      preset: 'ts-jest',  

      // 使用一个或多个项目运行测试  
      // projects: undefined,  

      // 使用此配置选项向 Jest 添加自定义报告器  
      // reporters: undefined,  

      // 在每次测试前自动重置模拟状态  
      // resetMocks: false,  

      // 在每次测试前重置模块注册表  
      // resetModules: false,  

      // 一个指向自定义解析器的路径  
      // resolver: undefined,  

      // 在每次测试前自动恢复模拟状态和实现  
      // restoreMocks: false,  

      // Jest 应该扫描测试和模块的根目录  
      // rootDir: undefined,  

      // Jest 要搜索文件的路径列表  
      // roots: [  
      //   "<rootDir>"  
      // ],  

      // 允许你使用自定义测试运行器而不是 Jest 的默认测试运行器  
      // runner: "jest-runner",  

      // 在每个测试前运行以配置或设置测试框架的模块路径列表  
      setupFiles: [  
        '<rootDir>/jest.polyfills.ts'  
      ],  

      // 在每个测试前运行的配置或设置测试框架的模块路径列表  
      setupFilesAfterEnv: [  
        '<rootDir>/jest.setup.ts'  
      ],  

      // Jest 认为测试慢于多少秒时报告为慢测试  
      // slowTestThreshold: 5,  

      // 一个自定义快照序列化器模块的路径列表,Jest 用于快照测试  
      // snapshotSerializers: [],  

      // 测试环境  
      testEnvironment: "jsdom",  

      // 测试环境选项  
      testEnvironmentOptions: {  
        customExportConditions: [''],  
      },  

      // 在测试结果中添加位置字段  
      // testLocationInResults: false,  

      // Jest 用于检测测试文件的 glob 模式列表  
      // testMatch: [  
      //   "**/__tests__/**/*.[jt]s?(x)",  
      //   "**/?(*.)+(spec|test).[tj]s?(x)"  
      // ],  

      // 匹配测试路径的正则表达式模式数组,匹配的测试将被跳过  
      // testPathIgnorePatterns: [  
      //   "/node_modules/"  
      // ],  

      // Jest 用于检测测试文件的正则表达式模式数组  
      // testRegex: [],  

      // 一个允许使用自定义结果处理器的选项  
      // testResultsProcessor: undefined,  

      // 一个允许使用自定义测试运行器的选项  
      // testRunner: "jest-circus/runner",  

      // 一个正则表达式映射到路径转换器的映射  
      // transform: undefined,  

      // 在所有源文件路径之前匹配的正则表达式数组,匹配的文件将跳过转换  
      // transformIgnorePatterns: [  
      //   "/node_modules/",  
      //   "\\.pnp\\.[^\\/]+$"  
      // ],  

      // 在所有模块之前匹配的正则表达式数组,模块加载器将自动为它们返回一个模拟  
      // unmockedModulePathPatterns: undefined,  

      // 指定每个单独的测试应该在运行时报告  
      // verbose: undefined,  

      // 在监视模式下重运行测试之前,在源文件路径中匹配的正则表达式数组  
      // watchPathIgnorePatterns: [],  

      // 是否使用 watchman 进行文件爬行  
      // watchman: true,  
    };  

    export default config;
  • Jest Setup file for references
    import '@testing-library/jest-dom'

5. Write API handlers

  • Create a handlers folder in your project based on your choice, but it will be good if you can separate a file for each entity, such as users, and handle its success and error cases in that file only.

  • Please find below an example of how we handle the process of retrieving user API responses, including both success and error messages.
    // mock/handlers/users.ts  
    import { http, HttpResponse } from "msw";  

    const host = 'http://localhost:8080';  

    const user = {  
      name: "santosh shinde",  
      blog: "http://blog.santoshshinde.com/",  
      linkedin: "https://www.linkedin.com/in/santosh-shinde-54454635/",  
    };  

    // for success response  
    const getUserSuccess = http.get(`${host}/api/user`, () => {  
      return HttpResponse.json(user);  
    });  

    // for error response  
    const getUserError = http.get(`${host}/api/user`, () => {  
      return new HttpResponse(null, { status: 500 });  
    });  

    export { getUserSuccess, getUserError };

6. Write Test cases

  • You can create test cases based on the use cases and API calls you are implementing in your project. Here is an example where we have a user endpoint and are attempting to simulate both success and error states.
    import React from "react";  
    import { Provider } from "react-redux";  

    import { render, screen, waitFor } from "@testing-library/react";  

    import App from "./App";  
    import { store } from "./store";  
    import { getUserError, getUserSuccess, user } from "../mock/handlers/users";  
    import { mockServer } from "../mock/server";  

    describe("App", () => {  
      // create mock server instance  
      const server = mockServer();  

      beforeEach(() => {  
        // reset handlers  
        server.resetHandlers();  
      });  

      it("renders success state with data", async () => {  
        // mock success api call using msw  
        server.use(getUserSuccess);  
        render(  
          <Provider store={store}>  
            <App />  
          </Provider>,  
        );  
        await waitFor(() => {  
          const linkElement = screen.getByText(user.name);  
          expect(linkElement).toBeInTheDocument();  
        });  
      });  

      it("renders error state", async () => {  
        // mock error api call using msw  
        server.use(getUserError);  
        render(  
          <Provider store={store}>  
            <App />  
          </Provider>,  
        );  
        await waitFor(() => {  
          const linkElement = screen.getByText('Error');  
          expect(linkElement).toBeInTheDocument();  
        });  
      });  
    });

If you are looking for a full example, please find below the ReactJS application repository, which I am continuously upgrading to sync with the latest standards and code base.

GitHub - santoshshinde2012/speedyreact-kit: The React Redux Esbuild Jest Starter is a modern…The React Redux Esbuild Jest Starter is a modern, efficient boilerplate designed to kickstart your React projects with…github.com

Thanks for reading. Please do share your comments and give a clap if this blog has added value to your learning.

Follow me on Twitter and LinkedIn __ and support my work by buying me a coffee.

点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消