这是我参与8月更文挑战的第1天,活动详情查看:8月更文挑战
概念
Jest
: Jest是一套测试框架,被Facebook
用来测试包括React
应用在内的所有JavaScript
代码。Jest
的一个理念是提供一套完整集成的 “零配置” 测试体验。- 官方推荐
- 支持输出详细的代码覆盖率报告
- 强大的
mock
函数 - 灵活方便引入第三方库
react-testing-library
: 一个DOM
测试库,官方推荐的Jest
的第三方库,类似于jQuery
,提供更简洁的Api,如定位元素,断言等- 思想:区别于enzyme,
react-testing-library
主要从用户角度去写单元测试,比如一个“详情”按钮,功能是点击按钮有弹窗,且发送请求,然后显示页面详细信息;那么我测试的思路点可以是,页面有一个按钮、文字为“详情”、点击后,页面会有个弹窗、弹窗内容显示“...”;而不用去关心组件内部代码的实现细节,比如props
传值等等 - 单元测试编写思路,参考ARRANGE-ACT-ASSERT模式
- Arrange:准备好需要测试的内容,比如
render
出组件对应的dom
元素;比如初始化一些变量值 - Act:包含组件的功能,可能是用户的交互如
userEvent
:click
;或者发送异步请求使用async...await
;或者调用某些方法; - Assert:断言
expect
预期的结果。Act
后会导致某些预期的反应,比如click
后页面元素的变化;异步请求后,数据的变化;方法的调用次数等等;
- Arrange:准备好需要测试的内容,比如
- 思想:区别于enzyme,
使用环境
- 项目如果是使用Create-React-App创建的,则默认已安装
@testing-library/jest-dom: "^5.14.1"
@testing-library/react: "^11.2.7"
@testing-library/user-event: "^12.8.3"
- 根据项目具体情况可额外添加其它依赖,如:
@testing-library/react-hooks
- 可在
package.json
中添加jest
(与script
同级),或者在项目中添加jest.config.ts
文件"jest": { "verbose": true, // 是否输出 descripe 和 test/it 中的测试描述信息 "collectCoverage": true, // 是否输出测代码试覆盖率 "setupFilesAfterEnv": [ "<rootDir>/src/setupTests.ts" // 在每个测试文件执行之前调用一些默认执行代码的文件路径 ] },
- 下面为
verbose
属性配置为false、true
后,输出信息的区别 - 下面为
setupTests.ts
文件中可配置默认执行代码例子
import React from 'react' import '@testing-library/jest-dom'; import { renderHook } from "@testing-library/react-hooks"; beforeEach(() => { return renderHook(() => { ... }) }) ...
- 下面为
知识点:react-testing-library
- DOM API
- render:
Example:function render( ui: React.ReactElement<any>, options?: {通常比较少用到,包含container、baseElement等可配置选项} ):RenderResult // RenderResult返回一些元素定位方法:getByText、queryAllByTestId等 eg:const {getByText, queryAllByTestId} = render(<Component />) // 也可以直接调用render,引入screen调用
// my-component.test.ts import {render} from '@testing-library/react' import userEvent from '@testing-library/user-event' import '@testing-library/jest-dom' import MyComponent from './my-component' // 使用render方法渲染组件或者dom,使用定位方法如 getByText 定位元素,然后断言 test('test Hello World', ()=> { const {container, getByText} = render(<MyComponent/>) // Arrange expect(getByText('Hello World').toBeInTheDocument() })
- cleanup:
- Unmounts React trees that were mounted with render
- 防止内存泄漏 Example:
import {render, cleanup} from '@testing-library/react' // 结合钩子函数使用,在每次调用测试前清楚之前渲染的dom afterEach(cleanup)
dom
定位方法-
getBy:定位页面已存在dom元素,如果不存在则抛出异常
-getByText: find by element text content
-getByRole: find by aria role
-getByLabelText: find by label or aria-label text content
-getByPlaceholderText: find by input placeholder value
-getByAltText: find by img alt attribute
-getByTitle: find by title attribute or svg title tag
-getByDisplayValue: find by form element current value
-getByTestId: find by data-testid attribute -
queryBy: 定位页面不存在的dom元素,如果不存在,返回null,不抛出异常(具体方法同上)
-
fintBy: 定位页面当中的异步元素,如果不存在,抛出异常(具体方法同上)
-
下面可以看到三大类方法区别:
-
- render:
- 异步方法结合
async...await
使用- waitFor(Promise):会一直等到内部函数执行完毕或者抛出异常为止
- waitForElementToBeRemoved(Promise):直到内部函数不返回Dom元素为止
- userEvent
click(element)
:单击dbClick
:双击async type(element, text, [options])
:输入文本selectOptions(element, values)
:表单选择tab({shift, focusTrap})
:模拟 tab 键(切换 focus)
- 补充Jest快照测试
snapshot
import React from 'react'; import renderer from 'react-test-renderer'; import Link from '../Link.react'; it('renders correctly', () => { const tree = renderer .create(<Link page="http://www.facebook.com">Facebook</Link>) .toJSON() expect(tree).toMatchSnapshot() })
总结
学习单元测试,结合ARRANGE-ACT-ASSERT这个思路,根据官方文档掌握对应每一步要用的api
,按照用户的角度去写单元测试就很简单了,通常就是以下三步:
- Arrange:元素定位结合
render
,jest-dom
——getby/queryby/findby...
,jest.mock()
...等等 - Act:
userEvent
,waitFor
,async...await
...等等 - Assert: 结合jest-dom-
toBeInTheDocument
,toBeChecked
等等