jestjs.io
一 简单配置
npx jest --init 一一配置环境 覆盖率 清除mock调用
npx jest --coverage 生成caverage目录
"scripts": {
"coverage": "node scripts/test.js --coverage --watchAll=false"
}
package.json
“scripts”: {
"test": "jest --watchAll",
// a模式:监听所有测试文件变化,一个文件改了运行全部用例
"test:o": "jest --watch",
// 进入o模式
}
Watch Usage
- Press
fto run only failed tests. (只运行失败的用例) - Press
oto only run tests related to changed files. (只运行修改文件相关的用例,需配合git) - Press
tto filter by a test name regex pattern. (根据用例的名字运行用例) - Press
pto filterr by a filename regex pattern. (根据文件名运行用例) - Press
qto quit watch mode. 退出监听
二 常用点
describe('类型', ()=>{
test('test', ()=>{
// 类型相关
expect(2+2).toBe(4)
expect(2+2).not.toBe(5)
expect(Null).toBeNull()
expect(1).toBeTruthy()
expect(0).toBeFalsy()
expect(undefined).toBeUndefined()
expect(1).toBeDefined()
// 数字相关
expect(12).toBeGreaterThan(11)
expect(10).toBeLessThan(11)
expect(10).toBeGreaterThanOrEqual(10) // >=
expect(10).toBeLessThanOrEqual(10) // >=
expect(0.1 + 0.2).toBeCloseTo(0.3)
// 字符串相关
expect('http://www.abc.com').toMatch(/abc/)
// Array, Set相关
expect(['a','b','c']).toContain('b')
// 异常
const throwNewError = () =>{
throw new Error('this si a new error')
}
expect(throwNewError).toThrow('this is a new error')
// 匹配对象内容
expect({name:'viking'}).toEqual({name: 'viking'})
// snapshot
// npm test 后 w u 更新全部快照
// w i 逐个更新快照
expect(wrapper).toMatchSnapshot()
// 对于类new Date()类型不定值
expect(wrapper).toMatchSnapshot({
time: expect.any(Date)
})
})
test('should trigger the correct function callbacks', ()=>{
const handleModifyItem = jest.fn();
const wrapper = shallow(<PriceList handleModifyItem={handleModifyItem}/>)
wrapper.find('.submit').simulate('click')
expect(handleModifyItem).toHaveBeenCalledWith(1)
expect(handelClick).toHaveBeenCalledWith(
expexct.objectContaining({
'action': 'click',
'error': false0 07
})
)
})
})
三 Enzyme
挂载 Enzyme
1 src/utils/testSetup.js
import Enzyme from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';
Enzyme.configure({adapter: new Adapter()})
2 jest.config.js
"setupFilesAfterEnv": [
'<rootDir>/src/utils/testSetup.js'
]
使用Enzyme
import React from 'react';
import { shallow } from 'enzyme';
let wrapper;
describe('', ()=>{
beforeEach(()=>{
wrapper = shallow(<TotalPrive {...props} />)
// 打印出DOM
console.log(wrapper.debug())
})
test('should render the compoent to match snapshot', ()=>{
expect(wrapper).toMatchSnapshot()
})
test('test', ()=>{
expect(wrapper.find('.income span').text()).toEqual('1000')
})
test('should render correct icon and price for each item', ()=>{
const iconList = wrapper.find('list-item').first().find(Icon)
//const icon5 = wrapper.find('list-item').at(5)
expect(iconList.length).toEqual(3)
})
test('click the year item, should trigger the right status change', ()=>{
wrapper.find('submit').sumilate('click')
expect(wrapper.find('.years-range').first().hasClass('active')).toEqual(true)
expect(wrapper.state('selectedYear')).toEqual(2014)
})
})
01 容器组件 test, 确认传入了正确的数据即可,组件内部有自己的UT
beforeEach(()=>{
wrapper = shallow(<TotalPrive {...props} />)
})
test('should render the default layout', ()=>{
expect(wrapper.find(PriceList).length).toEqual(1)
expect(wrapper.find(ViewTab).props().activeTab).toEqual(LIST_VIEW)
})
02 工具函数
export const getInputValue = (selector, wrapper) =>(
wrapper.find(selector).instance().value
)
export const setInputValue = (selector, newValue, wrapper) =>{
wrapper.find(selector).instance().value = newValue
}
// 解耦 test 与CSS
// <div data-test='container'> ... </div>
export const findTestWrapper = (wrapper, tag) =>{
return wrapper.find(`[data-test="${tag}"]`)
})
const container = findTestWrapper(wrapper, 'container')
expect(container.length).toBe(1)
四 Jest 钩子
describe('counter', ()=>{
let counter = null;
beforeAll(()=>{
//全局准备
console.log('beforeAll')
})
beforeEach(()=>{
// 每次运行用例前调用
counter = new Counter()
})
afterEach(()=>{
// 每次运行用例后调用
counter = new Counter()
})
afterAll(()=>{
//全局清理
console.log('afterAll')
})
describe('add', ()=>{
test('addOne', ()=>{
counter.addOne()
})
// test.only只执行这个用例(临时调试)
// 上面的钩子会被触发
test.only('addTwo', ()=>{
counter.addTwo()
})
})
describe('minus', ()=>{
test('minusOne', ()=>{
counter.minusaddOne()
})
test('minusTwo', ()=>{
counter.minusTwo()
})
})
})
五 异步测试
01 测试回调
export const fetchData =(fn)=>{
axios.get('http://www.abc.com/demo.json').then((response)=>{
fn(resoponse.data)
})
}
test('', (done)=>{
fetchData((data)=>{
expect(data).toEqual({success: true})
done()
})
})
02 测试promise
export const fetchData = () => {
return axios.get('http://www.abc.com/demo.json')
}
test('success', ()=>{
// 注意要把promise return 出去
return fetchData().then((response)=>{
expect(response.data).toEqual({
success: true
})
})
})
test('404', ()=>{
// 注意确保expect至少调用一次
expect.assertions(1);
return fetchData().catch((e)=>{
expect(e.toString().indexOf('404') > -1).toBe(true)
})
})
或者 toMatchObject
test('success', ()=>{
// toMatchObject 包含对象
return expect(fetchData().resolves.toMatchObject({
data: {
success: true
}
}))
})
test('404', ()=>{
return expect(retchData()).resolves.toThrow()
})
或者 async await
test('success', async ()=>{
const response = await fetchData()
expect(response.data).toEqual({
success: true
})
})
test('404', async ()=>{
// 确保expect执行1次
expect.assertions(1)
try{
await fetchData()
}catch(e){
expect(e.toString().indexOf('404') > -1).toBe(true)
}
})
六 Mock
01 mock function
test('runCallback', ()=>{
const func = jest.fn();
// func第一次执行时返回abc
func.mockReturnValueOnce('abc')
// func按顺序返回
func.mockReturnValueOnce('abc').mockReturnValueOnce('e').mockReturnValueOnce('f')
// func每次执行时返回efg
// const func = jest.fn(()=>{return 'efg'});
// func.mockReturnValue('efg')
runCallback(func)
expect(func).toBeCalled()
runCallback(func)
expect(func.mock.calls.length).toBe(2)
})
func.mock得值

01 mock 异步函数
demo.js
export const fetchData = ()=>{
return axios.get('/').then(res=>res.data)
// data:"(function(){return '123'})()"
}
demo.js同级目录下__mocks__/demo.js
export const fetchData = ()=>{
return new Promise((resolved, reject)=>{
resolved("(function(){return '123'})()")
})
}
demo.test.js
jest.mock('./demo');
// jest.unmock('./demo') 取消mock
// jest.config.js 开启 automock: true 效果相同 jest.mock('./demo')
import {fetchData} from './demo'; // mock fetchData
const {getNumber} = jest.requireActual('./demo'); // unmock getNumber
test('', ()=>{
return fetchData().then(data=>{
expect(eval(data)).toEqual('123')
})
})
02 mock axios 其一
demo.js
export const getData = () => {
return axios.get('/api').then(res => res.data)
}
demo.test.js
import axios from 'axios';
jest.mock('axios')
test('getData', async ()=>{
axios.get.mockResolvedValue({data: 'hello')
// 只mock一次
// axios.get.mockResolvedValueOnce({data: 'hello')
await getData().then((data)=>{
expect(data).toBe('hello')
})
})
03 mock axios 其二
* 01 src/mocks/axios.js
import {testCategories, testItems} frim '../tsetData'
export default {
get: jest.fn((url) => {
if(url.indexOf('categories') > -1){
return Primise.resolve({data: testCategories})
}else if(url.indexOf('items?') > -1){
return Primise.resolve({data: testItems})
}else if (url.indexOf('items/') > -1){
return Promise.resolve({data: {...testItems[0], id: 'testID'}})
}
})
}
* 02 App.test.js
import mockAxios from './__mocks__/axios'
import {testCategories, testItems} frim '../tsetData'
const waitForAsync = ()=> new Promise(resolve => setImmediate(resolve))
describe('waitForAsync 方式', ()=>{
afterEach(()=>{
jest.clearAllMocks()
})
it('', async()=>{
const wrapper = mount(<APP />)
expect(mockAxios.get).toHaveBeenCalledTimes(2)
//等待setState生效
await waitForAsync()
const currentState = wrapper.instance().state
expect(Objext.keys(currentState.items).length).toEqual(testItems.length)
})
})
describe('nextTick 方式', ()=>{
afterEach(()=>{
jest.clearAllMocks()
})
it('', (done)=>{
const wrapper = mount(<APP />)
expect(mockAxios.get).toHaveBeenCalledTimes(2)
process.nextTick(()=>{
wrapper.update();
const currentState = wrapper.instance().state
expect(Objext.keys(currentState.items).length).toEqual(testItems.length)
done()
})
})
})
04 mock timers
timer.js
export default (callback)=>{
setTimeout(()=>{
callback()
},3000)
}
timer.test.js
import timer from './timer'
test('timer', (done)=>{
timer(()=>{
expect(1).toBe(1)
done()
})
})
better timer.test.js
import timer from './timer'
beforeEach(()=>{
// useFakeTimers 放在 beforeEach 避免 advanceTimersByTime 跨test相互影响
jest.useFakeTimers()
})
test('timer', ()=>{
const fn = jest.fn();
timer(fn);
jest.runOnlyPendingTimers();// 马上执行当前timers 避免等待时间
jest.runAllTimers() // 马上执行全部timers 比如嵌套timers
// advanceTimersByTime 可替代上面快进时间
jest.advanceTimersByTime(1000) // 快进1秒
expect(fn).toHaveBeenCalledTimes(1)
})
05 mock Class
demo.js
import Util from './util'
//Util 是异常复杂的类
const demoFunction = (a,b)=>{
const util = new Util()
util.a(a)
util.b(b)
// UT不应关心util内部和返回值,只关心a b是否调用
}
expect default demoFunction
方式1 demo.test.js
jest.mock('./util')
import Util from './util'
// jest 将util转化为
// const Util = jest.fn()
// Util.a = jest.fn()
// Util.b = jest.fn()
import demoFunction from './demo';
test(''()=>{
demoFunction()
expect(Util).toHaveBeenCalled()
expect(Util.mock.instances[0].a).toHaveBeenCalled()
expect(Util.mock.instances[0].b).toHaveBeenCalled()
})
方式2 对util深层次定制mock util.js同级目录 mocks util.js
const Util = jest.fn()
Util.prototype.a = jest.fn()
Util.prototype.b = jest.fn()
expect default Util;
如不想用__mocks__文件夹形式,也可用回调
jest.mock('./util', ()=>{
const Util = jest.fn()
Util.prototype.a = jest.fn()
Util.prototype.b = jest.fn()
return Util;
})
06 mock actions promise
import {Create} from '../Create';
const history = {push:()=>{}}
const actions = {
getEditData: jest.fn().mockReturnValue(Promise.resole({editItem: testItem}))
}
describe('', ()=>{
it('', ()=>{
const wrapper = mount(
<Create actions={actions} history={history}/>
)
expect(actions.getEditData).toHaveBeenCalledWith(testItem.id)
})
})
05 mock input file
test('upload image', done => {
const mockFile = new File(['abc'], 'abc.png', {
type: 'image/png'
})
const props = {
updatePicture: ({picture, isError})=>{
expect(isError).toEqueal(false);
expect(picture).toBeTruthy();
done();
}
}
const UploadComponent = mount(<Upload {...props});
UploadComponent.find('input').simulate('change',{
target: {},
detaTransfer: {
files: [mockFile]
}
})
})
06 mock document event
test('after the dropdown is shown, click document should close the dropdown', ()=>{
let eventMap = {}
document.addEventListener = jest.fn((event, cb) => {
eventMap[event] = cb
})
const wrapper = shallow(<TotalPrive {...props} />)
wrapper.find('.dropdown-toggle').simulate('click')
expect(wrapper.state('isOpen')).toEqual(true)
expect(wrapper.find('.dropdown-ment').length).toEqual(1)
eventMap.click({
target: ReactDOM.findDOMNode(wrapper.instance())
})
expect(wrapper.state('isOpen')).toEqual(true)
eventMap.click({
target: document
})
expect(wrapper.state('isOpen')).toEqual(false)
})
七 UT插件
1 jsdom 模拟DOM

2 Enzyme 组件rendering
Shallow Rendering 浅render
Full Rendering 深render
Static Rendering 静态render
3 Nock 模拟HTTP

4 Sinon 函数跟踪

5 Istanbul


5 Jest
(文件路径不能有空格)


6 jest-enzyme
jest.config.js
"setupFilesAfterEnv": [
'./node_modules/jest-enzyme/lib/index.js'
]
const container = wrapper.find('[data-test="container"]')
expect(container.length).toExist()
expect(container.length).toHaveProp('title', 'dell lee')