jest.config.js
的一些常见配置属性如下:
module.exports = {// 可以指定测试环境testEnviroment: 'jest-environment-node' | 'jest-enviroment-jsdom',// 指定模块加载目录moduleDirectories: ['node_modules', path.join(__dirname, 'src'), 'shared']// identity-obj-proxy 支持在 jest 中引入 css, 同时支持 css 的模块化moduleNameMapper: {"\\.(css|less|scss)$": "identity-obj-proxy",},// before jest is loaded(不依赖 jest)setupFiles: []// after jest is loaded(依赖 jest)setupTestFrameworkScriptFile: require.resolve('./test/setup-tests.js')// 测试覆盖率收集目录collectCoverageFrom: ['src/**/*.js']// 指定测试覆盖率需要需要达到的阈值coverageThreshold: {global: {statements: 80,branches: 80,lines: 80,functions: 80,}}// 增强 watch 模式体验: $ npm install --save-dev jest-watch-typeaheadwatchPlugins: ['jest-watch-typeahead/filename','jest-watch-typeahead/testname',]}
jest-emotion: css 中具体样式发生更改便重新生成 snapshot。
jest-dom
封装了测试 dom 的方法。报错的信息可以更加准确。
import 'jest-dom/extend-expect'
此时可以使用如下方法:
expect(input).toHavaAttribute('type', 'number') // 是否有某个属性expect(..).toHaveTextContent() // 是否有某个内容
dom-test-library
的优势。
import { queries } from 'dom-testing-library'
@testing-library/react
在 dom-test-library
的基础上查找 React 组件。
import 'react-testing-library/cleanup-after-each' // 自动完成每次的回收
react-testing-library
中的 debug 函数来对子组件进行断点调试。test('...', () => {const { debug } = render(<Component />)debug()// ordebug(<SomeComponent />)})
import { fireEvent } from 'react-testing-library'fireEvent.change()
几种断言方式
方式一: expect(container).toHaveTextContent(/the number is invalid/i)
方式二: getByText(/the number is invalid/i)
方式三: expect(getByText(/the number is invalid/i)).toBeTruthy()
方式四: 配合 data-testid
属性可以使用 expect(getByTestId('...')).toHaveTextContent(/the number is invalid/i)
Test prop updates with react-testing-library
test('...', () => {const { rerender } = render(<Component />)rerender(<SomeComponent />)})
getByLabelText
(form inputs)getByPlaceholderText
(only if your input doesn’t have a label — less accessible!)getByText
(buttons and headers)getByAltText
(images)getByTestId
(use this for things like dynamic text or otherwise odd elements you want to test)query
打头的替代方法。以 query
开头的方法找不到的话会返回 null, 以 get
开头的方法找不到的话会 throw。findBy
打头的替代方法,其用于异步场景,是 getBy 与 waitFor 参数的组合用方法。link如果这些都不会让你确切地知道你在找什么, render 方法也会返回映射到 container 属性的 DOM 元素,所以也可以像 container.querySelector('body #root')
一样使用它。
import { render, fireEvent, wait } from 'react-testing-library'import {loadGreeting as mockLoadGreeting} from '../api'jest.mock('../api', () => {return {loadGreeting: jest.fn(subject =>Promise.resolve({data: {greeting: `Hi ${subject}`}}),),}})test('loads greetings on click', () => {const {getByLabelText, getByText, getByTestId} = render(<GreetingLoader />)const nameInput = getByLabelText(/name/i)const loadButton = getByText(/load/i)nameInput.value = 'Mary'fireEvent.click(loadButton)await wait(() => expect(getByTestId('greeting')).toHaveTextContent())expect(mockLoadGreeting).toHaveBeenCalledTimes(1)expect(mockLoadGreeting).toHaveBeenCalledWith('Mary')})
比如 react-transition-group
动画库也是存在异步库, 它会在 1s 后将 Children 隐藏, 这时候可以使用 mock
来处理。
jest.mock('react-transition-group', () => {return {CSSTransition: props => (props.in ? props.children : null),}})
console.error()
mock 掉beforeEach(() => {jest.spyOn(console, 'error').mockImplementation(() => {})})afterEach(() => {console.error.mockRestore()})
// componentDidCatch 里的两个参数const error = expect.any(Error)const info = {componentStack: expect.stringContaining('Bomb')}expect(mockReportError).toHaveBeenCalledWith(error, info)
当测试需要请求后端接口数据的 UI 组件(比如图片上传组件), 为了防止接口不稳定等影响到测试用例通过, 通常需要对请求后端接口数据进行 mock。
当需要测试接口返回的真实数据时可以对其进行集成测试。
jest.spyOn(global, 'fetch').mockImplementation(() => {Promise.resolve({json: () => Promise.resolve(mockData)})})
当组件中需要测试浏览器 api 时,同样可以使用 jest.spyon 来达到目的,比如测试 scrollTo。
jest.spyOn(window, 'scrollTo').mockImplementation(({ top }: any) => {document.documentElement.scrollTop = top})
在使用 JEST 测试一些需要浏览器位置信息的组件(比如 PullToRefresh、Scrollbar 组件等等)时, 需要将浏览器节点信息给 mock 掉。
函数力度拆细
的原因。如果存在对当前组件的测试影响不大的第三方模块, 可以将相关模块/组件进行 mock, 从而可以提高测试的效率。
jest.mock('someComponent', () => {return (props) => {return <span>mock Component</span>}})
如果测试用例中遇到 setTimeout(fn, 5000)
真的等上 5s 后才执行 fn 测试效率是非常低效的, 因此可以使用 jest 提供的 jest.useFakeTimers()
来 mock 与时间有关的 api。
// mocks out setTimeout and other timer functions with mock functions.jest.useFakeTimers()// use jest.runAllTimers() to make sure all perf of callback.jest.runAllTimers()// move ahead in time by 100msact(() => {jest.advanceTimersByTime(100)})
jest.mock('../../utils', () => {return {isIOS: true}})
act 确保其函数里跟的单元方法(比如 rendering、用户事件、数据获取)在执行步骤 断言(make assertions)
之前已经全部执行完。
act(() => {// render components})// make assertions
测试函数有两种风格, BDD(行为驱动开发) 以及 TDD(测试驱动开发)。
foo.should.equal('bar')
或者 expect(foo).to.equal('bar')
;assert.equal(foo, 'bar', 'foo equal bar')
;下面我们来书写基于 BDD 风格的 test 函数:
async function test(title, callback) {try {await callback()console.log(`✓ ${title}`)} catch (error) {console.error(`✕ ${title}`)console.error(error)}}
expect
函数:
function expect(actual) {return {toBe(expected) {if (actual !== expected) {throw new Error(`${actual} is not equal to ${expected}`)}}}}
应用:
const sum = (a, b) => a + btest("sum adds numbers", async () => {const result = await sum(3, 7)const expected = 10expect(result).toBe(expected)})
如果在 '@testing-library/react' 中测试某些组件暴露给业务方的钩子, 记录了下可以这样子测试
it('test instance exist', () => {let instancerender(<Componentref={node => {instance = node}}/>)expect(Object.prototype.toString.call(instance.A)).toBe('[object Function]')expect(Object.prototype.toString.call(instance.B)).toBe('[object Function]')})