使用 Mocha + chai 测试 React 应用


最近在继续开发之前的 Kindle UI 库,想到该写一下测试,于是决定使用 Mocha 的测试方案(尝试一些新东西)。

(此段由GPT生成👉)在开发过程中,保证代码质量和正确性是非常重要的。测试是达到这个目的的有效方法之一,特别是在 JavaScript 开发中。在 React 应用开发中,Mocha 和 Chai 是两个非常流行的测试框架。本文将介绍如何使用 Mocha 和 Chai 测试 React 应用。

为什么不用 react 官方推荐的 Jest 测试?

jest 最初是为测试 react app 设计的。相比 Jest, Mocha 具有更高的灵活性(后者可以且必须进行额外的配置,而前者开箱即用),可以在浏览器和 node 环境下运行,并且支持一些复杂的语句例如:

1expect(person.age).to.be.lte(35).and.to.be.gte(18);Q
2

由于我的项目并非单个 react app 而是 monorepo,所以在 workspace 根目录使用 mocha 无疑是更好的选择。

安装依赖

Mocha 本身是不支持 JSX 的,所以我们要安装一些依赖:

1yarn add mocha -D
2
3# Babel 配套插件
4yarn add -W -D @babel/preset-env @babel/preset-react @babel/preset-typescript @babel/register
5
6# 此依赖用于渲染 react, 请根据 react 版本选择依赖版本!
7yarn add react-test-renderer@17.0.2 -D -W

注意:如果你在 workspace 根目录安装测试依赖(推荐的做法),记得添加 -W 参数。

配置 Mocha

在项目根目录创建一个 .mocharc.js 文件,内容如下:

1module.exports = {
2	extension: ["js", "mjs", "ts", "tsx"],
3	ignore: ["**/build/**", "**/node_modules/**"],
4	recursive: true,
5	timeout: (process.env.CIRCLECI === "true" ? 5 : 2) * 1000, // Circle CI has low-performance CPUs.
6	reporter: "dot",
7	require: [require.resolve("./test/utils/setupBabel")],
8	"watch-ignore": [
9		".git",
10		"**/node_modules/**",
11		"**/build/**",
12		"docs/.next/**",
13	],
14};
15

在 /test/utils/setupBabel 目录创建一个 setupBabel.js 文件

1require("@babel/register")({
2	extensions: [".js", ".ts", ".tsx"],
3});

配置 Babel

有了 babel, 我们可以使用 cjs 格式的测试文件而无需进行 "type": "module" 这种令人讨厌的设置。

1// babel.config.js
2module.exports = function getBabelConfig(api) {
3	const useESModules = api.env(["legacy", "modern", "stable", "rollup"]);
4
5	const presets = [
6		[
7			"@babel/preset-env",
8			{
9				browserslistEnv: process.env.BABEL_ENV || process.env.NODE_ENV,
10				modules: useESModules ? false : "commonjs",
11			},
12		],
13		[
14			"@babel/preset-react",
15			{
16				runtime: "automatic",
17			},
18		],
19		"@babel/preset-typescript",
20	];
21
22	const plugins = [];
23
24	return {
25		assumptions: {
26			noDocumentAll: true,
27		},
28		presets,
29		plugins,
30		ignore: [/@babel[\\\\|/]runtime/], // Fix a Windows issue.
31		overrides: [
32			{
33				exclude: /\\.test\\.(js|ts|tsx)$/,
34				plugins: ["@babel/plugin-transform-react-constant-elements"],
35			},
36		],
37	};
38};
39

测试

在 package.json 添加快捷脚本,此处请根据实际项目配置:

1"test:unit": "cross-env NODE_ENV=test mocha --config .mocharc.js 'packages/kindle-ui/**/*.test.{mjs,js,ts,tsx}' 'test/utils/**/*.test.{js,ts,tsx}'"
2

之后我们可以愉快地编写测试了。

关于 react-test-renderer 的更多用法,可以参考官方文档

此处列举一些常见测试场景供参考:

检查组件类型

1import * as React from "react";
2import { expect } from "chai";
3import renderer from "react-test-renderer";
4// 建议使用 link 后的包,以确保贴近实际场景。
5import { ListItem } from "@kindle-ui/core";
6
7describe("<ListItem />", () => {
8	describe("prop: component", () => {
9		it("renders a div", () => {
10			const component = renderer.create(<ListItem />);
11			expect(component.toTree().rendered).to.have.property("type", "div");
12		});
13
14		it("renders a link", () => {
15			const component = renderer.create(
16				<ListItem component="a" href="#" />
17			);
18			expect(component.toTree().rendered).to.have.property("type", "a");
19		});
20	});
21});
22

检查渲染结果

1it("render in Container", () => {
2	expect(() =>
3		renderer.create(
4			<Container>
5				<ListItem>test</ListItem>
6			</Container>
7		)
8	).not.to.throw();
9});
10

改进

react 官方提供的测试库功能比较局限,例如,我们无法测试一个元素是否可见,也无法模拟用户操作页面。

所以,我们可以使用@testing-library/react来进行进一步改进。

注意,node 环境下没有 document 对象,需要使用 JSDom 这个库模拟一个。

先为 mocha 添加钩子:

1// .mocharc.js
2
3module.exports = {
4    {/**... */}
5	beforeEach: () => {
6		const dom = new JSDOM("", {
7			pretendToBeVisual: true,
8			url: "<http://localhost>",
9		});
10		global.window = dom.window;
11	},
12};

更新测试代码:

1import { queries, within } from "@testing-library/react/pure";
2
3describe("general rendering", async () => {
4	it("render in Container", async () => {
5		render(<ListItem>KindleUI</ListItem>);
6
7		expect(
8			within(document.body, { ...queries })
9				.getByRole("ListItem")
10				.toHaveTextContent("KindleUI")
11		).to.be(true);
12	});
13});
14