背景
react官方推荐RTL(react testing library)
为什么不用 Enzyme,因为Enzyme shallow对react hooks的支持真的是一言难尽
并且enzyme-adapter-react只更新到16
创作者之一更是直言如果要支持react18的话,不仅adapter,连enzyme都需要重写,这对于三年没更新的enzyme来说基本是不可能的
目前react18刚更新的问题: next/jest 需要nextjs 12
@testing-library/react-hooks 目前只支持到 react 17,未来估计会适配react18
配置 jest
首先安装一下jest相关的依赖
npm install --save-dev jest @types/jest @jest/types jest-environment-jsdom
安装好了以后,初始化jest配置
npx jest --init
- 是否为package.json中添加test脚本,这个选yes就可以了
- 是否用ts,按照你的项目选
- 单测环境(jsdom):因为我们会涉及到 dom 的单测,不仅仅是纯逻辑,如果是纯逻辑的选 node。
- 是否需要覆盖率报告(no):暂时用不上,后面覆盖率章节会着重介绍。
- 编译代码(babel): 可以转 ES5,避免一些兼容性问题。
- 每次测试完是否清理 mock、实例等结果(yes): 每次测试完成后会清理 mock 等上次测试的结果,可以避免用例之间的互相影响
然偶在根目录已经生成了对应的 jest.config.ts 文件,大家也可以根据自己的需要增加额外的自定义配置,具体可以参考 Configuring。
因为选用的是babel,所以我们还要做一下相关配置
npm install --save-dev babel-jest
// 按需安装
@babel/core @babel/preset-env @babel/preset-react @babel/preset-typescript ts-node
在babel.config中添加自动引用依赖, 为它加上 runtime: "automatic"的配置,这样每个单测文件可以自动引用react依赖,(如果你指向配置next/babel,那么后面那些除了ts-node的插件可以不安装)
// ./babel.config.js
module.exports = {
presets: [
['next/babel'],
["@babel/preset-env", { targets: { node: "current" } }],
["@babel/preset-react",{ runtime: "automatic" }], // 自动导入react
"@babel/preset-typescript",
],
}
这样初步配置就基本完成了,我们可以在根目录创建一个test文件夹,新建一个最简单的单测文件(一定要带上.test这个后缀才会被jest识别成要执行的单测文件)
// App.test.ts
import React from "react";
test("test", () => {
expect(1 + 1).toBe(2);
});
然后运行
yarn test
即可得到结果
jest只会识别js,jsx文件,所以想要让他识别更多的文件,我们需要添加mock
npm install --save-dev identity-obj-proxy
// jest.config.ts
moduleNameMapper: {
".(css|less|sass|scss)$": "identity-obj-proxy"
},
transform: {
"^.+.(js|ts|tsx)$": "<rootDir>/node_modules/babel-jest"
},
配置 next/jest
next项目还是和正常的react项目有所不同的,具体可以参考next.js官方文档如何配置next+react testing library (ps next12有next/jest,11.0.0没有,所以找不到依赖可以升级一下)
// jest.config.js
import nextJest from 'next/jest'
const createJestConfig = nextJest({
// Provide the path to your Next.js app to load next.config.js and .env files in your test environment
dir: './',
})
const customJestConfig = {
// 这里面写你正常的jest配置
...
}
export default createJestConfig(customJestConfig)
配置 React Testing Library
安装依赖
- @testing-library/jest-dom:用于 dom、样式类型等元素的选取。
- @testing-library/react:提供针对 React 的单测渲染能力。
- @testing-library/user-event:用于单测场景下事件的模拟。
npm install --save-dev @testing-library/jest-dom @testing-library/react
// 按需安装
npm install --save-dev @testing-library/user-event
先在test/config文件夹下面配置文件
// test/config/jest-setup.js
import '@testing-library/jest-dom'
然后jest.config中全局引入环境jest-dom
// jest.config.ts
setupFilesAfterEnv: ["<rootDir>/test/config/jest-setup.js"],
然后在test文件夹下创建一个最简单的tsx单测文件
// test/AppReact.test.tsx
import { render, screen } from "@testing-library/react";
import "@testing-library/jest-dom";
import App from "@/pages/_app";
describe("test", () => {
test("unit test1", () => {
render(<App />);
expect(screen.getByText("渠道管理平台")).toBeInTheDocument();
});
});
// src/pages/_app.tsx
function CustomApp({ Component, pageProps }: any) {
return (
<div>管理平台</div>
);
}
export default CustomApp;
这时候你会发现这个报错
这是因为你在tsconfig.json和next.config.js里面的path(路径替换)都不会被jest识别,如果还想让路径替换生效,需要在jest.config.js里重新声明一下。声明规则可以参考
moduleNameMapper
改为
// jest.config.js
moduleNameMapper: {
'\\.(css|less|sass|scss)$': 'identity-obj-proxy',
'@/(.*)': '<rootDir>/src/$1',
'\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$': '<rootDir>/test/__mocks__/fileMock.js',
},
其他文件的Mock用的是第三行
// /test/__mocks__/fileMock.js
module.exports = 'test-file-stub';
然后就可以运行出正确结果
模拟next/config和next/router
在next项目中,免不了要使用next/config去调用当前配置,router去查看当前路径/获取query,这一部分其实官网有给出解决方案
点击前往next-router-mock
但是这个解决方案要安装很多依赖,在安@testing-library/react-hooks这个库时候发现他只支持react版本16和17,但是我们项目用的是18,所以只能另辟蹊径
经过多年的单测经验,只要直接粗暴的mock掉这两个依赖就好了,代码如下
import { render, screen } from "@testing-library/react";
import "@testing-library/jest-dom";
import App from "@/pages/_app";
// mock next/config
jest.mock('next/config', () => jest.fn().mockImplementation(() => {
return {
publicRuntimeConfig: {
API_HOST: 'api/',
STATIC_ASSETS_URL: '/static/assets'
}
};
}))
// mock next router
jest.mock('next/router', () => ({
useRouter: jest.fn().mockImplementation(() => {
return {
pathname: '/'
};
})
}))
describe("test", () => {
test("测试container是否正常运行", () => {
// 这里是为了防止传参类型AppProps报错所以传了Components和pageProps,如果没有这个问题可以不传
const wrapper = render(<App Component={() => <div>111</div>} pageProps={{}}/>);
expect(screen.getByText("管理平台")).toBeInTheDocument();
// screen.getByRole('button')
// expect(wrapper).toMatchSnapshot()
});
});
然后就可以正常运行了,运行后的图有很多项目路径,就不放了哈 当然老在Terminal中看覆盖很费劲,我们也可以在package.json中添加
"test:coverage": "jest --coverage"
然后运行
yarn test:coverage
在根目录会生成coverage文件夹,点里面随意的.html文件,然后点左上的ALL files你就能找到所有测到文件的覆盖率
点到指定文件,红色的就是没有覆盖到的行,黄色的就是分支没有覆盖的地方
如果遇到window.matchMedia is not a function这个报错 那是jsdom没有模拟到,可以参照jest官网的解决方案
参考文献
下一篇文章会写一下怎么去写测试用例