共计 5994 个字符,预计需要花费 15 分钟才能阅读完成。
技术交换群: https://fiora.suisuijiang.com/
原文链接: https://github.com/yinxin630/…
Jest 是一款简略, 容易上手且性能非常弱小的测试框架
装置
yarn add -D jest
应用
创立 test
目录, 增加 plus.spec.js
文件
describe('example', () => {it('should equal 2', () => {expect(1 + 1).toBe(2); | |
}); | |
}); |
执行 yarn jest
或者 yarn jest test/plus.spec.js
运行测试用例
胜利后果
失败后果
输入测试覆盖率
在根目录创立 jest.config.js
配置文件
module.exports = {collectCoverage: true,};
创立 plus.js
模块
module.exports = function plus(a, b) {return a + b;}
批改测试用例应用模块
const plus = require('../plus'); | |
describe('example', () => {it('should equal 2', () => {expect(plus(1, 1)).toBe(2); | |
}); | |
}); |
再次执行测试, 输入覆盖率如下
在浏览器中关上 coverage/lcov-report/index.html
能够浏览覆盖率后果页面
疏忽局部文件或者代码行的覆盖率
批改 plus.ts
模块, 增加更多分支
export default function plus(a: number, b: number) {if (a + b > 100) {return 0;} else if (a + b < 0) {return 0;} else {return a + b;} | |
} |
从新执行测试, 覆盖率输入后果
你能够欠缺测试用例, 或者可能有些文件 (譬如 config) 和代码分支并不需要测试, 能够将其在测试覆盖率后果中排除, 参考如下配置
- 疏忽目录下所有文件
在 jest.config.js
中增加
collectCoverageFrom: ['**/*.{ts,tsx}', | |
'!**/node_modules/**', | |
'!**/[directory path]/**', | |
], |
以 !
结尾的示意疏忽与其匹配的文件
- 疏忽单个文件
在该文件顶部增加 /* istanbul ignore file */
- 疏忽一个函数, 一块分支逻辑或者一行代码
在该函数, 分支逻辑或者代码行的上一行增加 /* istanbul ignore next */
反对 Typescript
执行 yarn add -D typescript ts-jest @types/jest
装置 typescript 和申明
并在 jest.config.js
中增加 preset: 'ts-jest'
将 plus.js
重命名为 plus.ts
export default function plus(a: number, b: number) {return a + b;}
同样的, 将 plus.spec.js
重命名为 plus.spec.ts
import plus from '../plus' | |
describe('example', () => {it('should equal 2', () => {expect(plus(1, 1)).toBe(2); | |
}); | |
}); |
执行测试, 后果和之前统一
执行单测时不校验 ts 类型
有时你可能会心愿不校验 ts 类型, 仅执行代码测试, 比方须要在 CI 中将类型校验和单元测试分为两个工作
在 jest.config.js
中增加如下内容
globals: { | |
'ts-jest': {isolatedModules: true,}, | |
} |
测试 React 组件
装置 react 依赖 yarn add react react-dom
和申明 yarn add -D @types/react
装置 react 测试库 yarn add -D @testing-library/react @testing-library/jest-dom
增加 typescript 配置文件 tsconfig.json
{ | |
"compilerOptions": { | |
"target": "es2018", | |
"strict": true, | |
"moduleResolution": "node", | |
"jsx": "react", | |
"allowSyntheticDefaultImports": true, | |
"esModuleInterop": true, | |
"lib": ["es2015", "es2016", "es2017", "dom"] | |
}, | |
"exclude": ["node_modules"] | |
} |
新增测试组件 Title.tsx
import React from 'react'; | |
function Title() { | |
return (<h1>Title</h1>); | |
} | |
export default Title; |
新增测试用例 test/Title.spec.tsx
/** | |
* @jest-environment jsdom | |
*/ | |
import React from 'react'; | |
import {render} from '@testing-library/react'; | |
import '@testing-library/jest-dom/extend-expect'; | |
import Title from '../Title'; | |
describe('Title', () => {it('should render without error', () => {const { getByText} = render(<Title />); | |
const $title = getByText('Title'); | |
expect($title).toBeInTheDocument();}); | |
}); |
执行 yarn jest test/Title.spec.ts
查看后果
解决动态资源援用
react 组件有时援用一些动态资源, 譬如图片或者 css 样式表, webpack 会正确的解决这些资源, 然而对 Jest 来讲, 这些资源是无奈辨认的
创立 Title.less
样式表
h1 {color: red;}
批改 Ttitle.tsx
, 增加款式援用 import './Title.less';
执行测试会报错
咱们须要配置 transform 对其解决
在根目录创立 jest.transformer.js
const path = require('path'); | |
module.exports = {process(src, filename) {return `module.exports = ${JSON.stringify(path.basename(filename))};`; | |
}, | |
}; |
这里是将资源文件名作为模块导出内容
批改 jest.config.js
增加如下配置
transform: {'\\.(less)$': '<rootDir>/jest.transformer.js', // 正则匹配, 解决 less 款式 | |
}, |
而后从新执行测试就能够了
解决 css in js
如果你应用了相似 linaria 这种 css in js 计划, 其中的 css 款式模板字符串是不反对运行时编译的
批改 Title.tsx
import React from 'react'; | |
import {css} from 'linaria'; | |
const title = css` | |
color: red; | |
`; | |
function Title() {return <h1 className={title}>Title</h1>; | |
} | |
export default Title; |
运行测试会报错
linaria 是通过 babel 插件将其预编译为 class 名的, 这里能够 mock 一下 css
函数, 返回一个随机值作为 class 名
在根目录创立 jest.setup.js
jest.mock('linaria', () => ({css: jest.fn(() => Math.floor(Math.random() * (10 ** 9)).toString(36)), | |
})); |
批改 jest.config.js
增加如下配置
setupFilesAfterEnv: ['./jest.setup.js'],
从新执行测试就能够了
测试交互事件
新增 Count.tsx
组件
import React, {useState} from 'react'; | |
function Count() {const [count, updateCount] = useState(0); | |
return ( | |
<div> | |
<span data-testid="count">{count}</span> | |
<button data-testid="button" onClick={() => updateCount(count + 1)}> | |
+1 | |
</button> | |
</div> | |
); | |
} | |
export default Count; |
新增 test/Count.spec.tsx
组件
/** | |
* @jest-environment jsdom | |
*/ | |
import React from 'react'; | |
import {render, fireEvent} from '@testing-library/react'; | |
import '@testing-library/jest-dom/extend-expect'; | |
import Count from '../Count'; | |
describe('Count', () => {it('should render without error', () => {const { getByTestId} = render(<Count />); | |
const $count = getByTestId('count'); | |
const $button = getByTestId('button'); | |
expect($count).toHaveTextContent('0'); | |
fireEvent.click($button); | |
expect($count).toHaveTextContent('1'); | |
}); | |
}); |
这里通过 testId
来查找元素, 应用 fireEvent 触发 click
事件
测试函数调用
新增 Button.tsx
组件
import React from 'react'; | |
type Props = {onClick: () => void; | |
}; | |
function Button({onClick}: Props) {return <button onClick={onClick}>button</button>; | |
} | |
export default Button; |
增加 test/Button.spec.tsx
测试用例
/** | |
* @jest-environment jsdom | |
*/ | |
import React from 'react'; | |
import {render, fireEvent} from '@testing-library/react'; | |
import '@testing-library/jest-dom/extend-expect'; | |
import Button from '../Button'; | |
describe('Button', () => {it('should render without error', () => {const handleClick = jest.fn(); // mock 函数 | |
const {getByText} = render(<Button onClick={handleClick} />); // 传递 props | |
const $button = getByText('button'); | |
fireEvent.click($button); | |
expect(handleClick).toHaveBeenCalled(); // 冀望其被调用}); | |
}); |
测试蕴含定时器的逻辑
// timer.ts | |
let cache = 'cache'; | |
export default function timer() {setTimeout(() => {cache = '';}, 1000); | |
return cache; | |
} |
// test/timer.spec.ts | |
import timer from '../timer' | |
jest.useFakeTimers(); // 代替原生计时器 | |
describe('timer', () => {it('should clear cache after timer out', () => {expect(timer()).toBe('cache'); | |
jest.advanceTimersByTime(1000); // 让计时器后退 1000ms | |
expect(timer()).toBe(''); | |
}) | |
}) |
mock 依赖模块
要测试的模块可能依赖于其余模块或者第三方 npm 包的后果, 咱们能够应用 Mock Functions 对其进行 mock
// test/mock.spec.ts | |
import {mocked} from 'ts-jest/utils'; | |
import plus from '../plus'; | |
jest.mock('../plus'); | |
describe('mock', () => {it('should return mock value', () => {mocked(plus). (50); | |
expect(plus(1, 1)).toBe(50); | |
}); | |
}); |
还有官网 mock axios npm 模块的例子 https://jestjs.io/docs/en/moc…
mock 环境变量和命令行参数
有的模块会从环境变量和命令行参数取值, 并且可能是在模块初始化时获取的
// process.ts | |
const {env, argv} = process; | |
export function getEnvironmentValue() {return env.Value;} | |
export function getProcessArgsValues() {return argv[2]; | |
} |
这种状况咱们须要在每个测试用例中, 应用动静 require 来运行时引入改模块, 并且设置其每次引入时删除 cache
// test/process.spec.ts | |
describe('mock process', () => {beforeEach(() => {jest.resetModules(); | |
}); | |
it('should return environment value', () => { | |
process.env = {Value: 'value',}; | |
const {getEnvironmentValue} = require('../process'); | |
expect(getEnvironmentValue()).toBe('value'); | |
}); | |
it('should return process args value', () => {process.argv = ['value']; | |
const {getProcessArgsValues} = require('../process'); | |
expect(getProcessArgsValues()).toBe('value'); | |
}); | |
}); |