本文将讨论为什么我们真正需要一个测试策略,如何利用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组件时,我们需要考虑以下几个因素。
- 使用 React 测试库 (RTL):RTL(React 测试库)提供了一种简单直接的方法来测试 React 组件。它鼓励测试组件的行为和用户交互,而非内部状态。
- 利用类型安全性:TypeScript 提供的类型安全性可以帮助提高测试的可维护性。TypeScript 可以检测组件 props 或状态的任何更改,并在测试代码中突出显示编译器错误,以相应地更新测试。
- 测试自定义 Hooks:虽然通常不需要单独测试 Hooks,但对于复杂或可重用的 Hooks,可以利用诸如
react-hooks-testing-library
等库独立于任何组件进行测试。 - 避免模拟组件级别的实现:可以使用像 MSW 这样的工具在网络级别拦截网络请求,可以在不模拟组件实现的情况下模拟 API 的响应。
拥有充分的单元测试和集成测试覆盖率是坚实的基础,但这还不够,通过加入有针对性的端到端测试并采用平衡的测试策略,这将有助于进一步提升应用程序的整体质量和可靠性。定期审查并调整测试方法,以适应代码库的变化和扩展。
测试三角形
这里有一个简化的测试金字塔版本,该金字塔来自2014年Google测试自动化大会开幕式上的演讲。
快速行动,不要把事情搞砸了。
我认为在上述提到的所有层次写测试是一种持续的贡献行为,应该包含在我们对功能开发的定义里。
关于80%代码覆盖率的误解:常见的坑在审查测试用例报告时,我们关注代码覆盖率,并根据这个来评估代码质量。但是结果却出乎我们的预料。这个过程实际上违背了编写这些测试用例的初衷。
为什么在这个场景下编写相关的测试用例很重要?Kent C. Dodds: 解释了为什么以及怎样编写测试用例。
让我们来看看几个涉及React TypeScript测试的情况,这些情况给我们提供了代码覆盖率方面的错误数据。
- 测试 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--------------------------------)
- 在你的项目里安装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.comThanks 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.
共同学习,写下你的评论
评论加载中...
作者其他优质文章