0%

jest-integrate-jest-to-react-app

今天来看一下如何在React项目中使用Jest进行单元测试。

创建React项目

我们使用Vite来创建React项目,命令如下:

1
npm create vite@latest react-app-jest --template react-ts

使用VSCode打开项目

使用如下命令打开项目

1
2
cd react-app-jest
code .

安装初始依赖

打开VSCode,选择菜单中的Terminal | New Terminal(或者快捷键Ctrl + Shift + ` )打开一个新的terminal,运行以下命令安装初始依赖:

1
npm install

运行项目

运行以下命令来启动项目,确保项目可以正常运行。

1
npm run dev

安装Jest相关的依赖

需要安装的依赖很多,运行以下命令来安装, npm i -D相当于npm install --save-dev.

1
npm i -D jest @types/jest ts-jest @testing-library/react @testing-library/jest-dom @testing-library/user-event jest-environment-jsdom ts-node

分别解释一下上面的包都有什么作用:

  • jest:Jest是一个JavaScript测试框架,主要用于单元测试和集成测试。
  • @types/jest:Jest的TypeScript类型定义文件。
  • ts-jest:Jest的TypeScript预处理器,允许你在Jest中使用TypeScript。
  • @testing-library/react:React Testing Library是一个用于测试React组件的库,提供了一些实用的工具和方法。
  • @testing-library/jest-dom:提供了一些自定义的Jest匹配器,用于测试DOM节点的状态。
  • jest-environment-jsdom:Jest的JS DOM环境,允许你在Node.js中模拟浏览器环境。
  • @testing-library/user-event:提供了一些实用的工具和方法,用于模拟用户事件。它比@testing-library/react中内置的fireEvent更好用。
  • ts-node:TypeScript执行器,允许你在Node.js中直接运行TypeScript代码。Jest通过这个模块来读取jest.config.ts文件。

配置Jest

jest.setup.ts

src目录下创建文件test.setup.ts,这个文件用来导入@testing-library/jest-dom,内容如下:

1
import '@testing-library/jest-dom';

mock文件

在项目根目录下创建一个mock文件夹,里面添加一个文件fileMock.ts,这个文件主要用来处理svg, gif, css等非javascript/json格式的文件,内容如下:

1
2
3
4
module.exports = {
__esModule: true,
default: 'test-file-stub',
}

在项目根目录下创建一个jest.config.ts文件,内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import { Config } from 'jest';

const config: Config = {
rootDir: './',
testEnvironment: 'jsdom',
setupFilesAfterEnv: ['<rootDir>/test.setup.ts'],
transform: {
'^.+\\.(ts|tsx)$': 'ts-jest',
},
moduleNameMapper: {
'\\.(jpg|jpeg|png|gif|svg|css)$': '<rootDir>/fileMock.js',
},
}

export default config;

tsconfig.json配置

添加如下内容到项目根目录下的tsconfig.json文件中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
"compilerOptions": {
"target": "ES2020",
"module": "ESNext",
"lib": [
"ES2020",
"DOM",
"DOM.Iterable"
],
"useDefineForClassFields": true,
"esModuleInterop": true,
"skipLibCheck": true,
"moduleResolution": "bundler",
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx"
},

创建测试文件

首先修改一下src/App.tsx文件,让它渲染一个h1标签,内容是Hello, world!

1
<h1>Hello, world!</h1>

然后在src目录下创建一个测试文件App.test.tsx,内容如下:

1
2
3
4
5
6
7
8
9
10
import { render, screen } from '@testing-library/react';
import App from './App';
import '@testing-library/jest-dom';
describe('App', () => {
test('renders learn react link', () => {
render(<App />);
const linkElement = screen.getByText(/Hello, world/i);
expect(linkElement).toBeInTheDocument();
});
});

运行测试

通过命令行运行

首先配置package.json,在package.json中添加以下脚本:

1
2
3
4
"scripts": {
"test": "jest",
"test:coverage": "jest --coverage",
}

然后在终端中运行以下命令来运行测试:

1
npm run test

如果你想查看测试覆盖率,可以运行以下命令:

1
npm run test:coverage

通过Jest插件运行测试

有两个常用的Jest插件可以使用,一个是Jest,另一个是Jest Runner。这两个插件都可以在VSCode的扩展市场中找到。

安装以后,打开测试文件App.test.tsx,你会看到每个测试用例旁边都有一个绿色的运行按钮,点击它就可以运行测试了。

也可以使用鼠标右键菜单中的Run Jest来运行测试。

troubleshooting

Support for the experimental syntax ‘jsx’ isn’t currently enabled

SyntaxError: Private field ‘#root’ must be declared in an enclosing class

如果遇到如下错误,仔细观察错误产生的位置,是在import App.css的地方,说明是css文件的问题。

1
2
3
4
5
6
7
8
9
10
11
12
SyntaxError: Private field '#root' must be declared in an enclosing class

1 | import { useState } from 'react'
> 2 | import './App.css'
| ^
3 |
4 | function App() {
5 | const [count, setCount] = useState(0)

at Runtime.createScriptFromCode (node_modules/jest-runtime/build/index.js:1505:14)
at Object.<anonymous> (src/App.tsx:2:1)
at Object.<anonymous> (src/App.test.tsx:3:1)

将CSS文件格式添加到jest.config.ts文件中的moduleNameMapper区块中,内容如下:

1
2
3
moduleNameMapper: {
'\\.(jpg|jpeg|png|gif|svg|css)$': '<rootDir>/fileMock.js',
},

require() of ES modules is not supported.

如果你遇到了如下错误,是因为你的jest.config.ts文件是ES Module格式的,而Jest默认使用CommonJS格式来解析配置文件。欲知详情请查看这里

1
2
3
Error: Jest: Failed to parse the TypeScript config file D:\personal\codes\react\vite-react-app-jest\jest.config.ts
Error: Must use import to load ES Module: D:\personal\codes\react\vite-react-app-jest\jest.config.ts
require() of ES modules is not supported.

解决办法也很简单,因为Jest的配置文件支持多种格式,我们姑且不实用ts格式,而是使用js格式来编写配置文件。将jest.config.ts文件重命名为jest.config.js,并将文件内容修改为如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const config = {
verbose: true,
rootDir: './',
testEnvironment: 'jsdom',
setupFilesAfterEnv: ['<rootDir>/src/test.setup.ts'],
transform: {
'^.+\\.(ts|tsx)$': 'ts-jest',
},
moduleNameMapper: {
'\\.(jpg|jpeg|png|gif|svg|css)$': '<rootDir>/fileMock.js',
},
};

export default config;

如此这般,Jest就不会在使用ts-node来解析配置文件了,也就不会再报上面的错误了。

Aggregate Error:

这个错误非常的诡异,出现的场景也不尽相同,我至今没有搞懂这个错误的根源到底是什么。

1
2
3
4
5
6
7
8
9
10
11
12
AggregateError

39 |
40 | test('shows validation errors when submitting empty form', async () => {
> 41 | render(<Login />);
| ^
42 |
43 | // Submit the form without filling any fields
44 | const loginButton = screen.getByTestId('login-button');

at aggregateErrors (node_modules/react/cjs/react.development.js:527:11)
at Object.<anonymous> (src/Login.test.tsx:41:11)

AggregateError是一个内置的JavaScript错误类型,表示多个错误的集合。这个错误通常是由于Promise.all()方法中的多个Promise被拒绝时抛出的。详情可以看这里

处理办法:
test.setup.ts文件中添加如下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
Object.defineProperty(window, 'matchMedia', {
writable: true,
value: (query: unknown) => ({
matches: false,
media: query,
onchange: null,
addListener: () => { },
removeListener: () => { },
addEventListener: () => { },
removeEventListener: () => { },
dispatchEvent: () => { },
})
});