初次执行会生成快照文件,第二次执行同样生成快照文件,并与第一次快照进行比较,快照内容相同则测试通过。
快照比对失败,可以用 u 键批量更新快照,也可以用 i 键来决定每一个快照的更新操作。
如果 generateConfig 返回的配置信息在每次函数执行都会发生变化,难道需要我们每次都去更新快照,那么这种情况该如何处理呢?
比如
const generateConfig= () => ({ host: 'localhost', port: 3000, time: new Date() })这里的 time 字段在每次函数执行的时候都会更新,从而导致快照比对失败,那么能否在进行快照对比的时候自动忽略掉 time 字段的比对呢,其实是可以的
test('test config snapshot', () => { expect(generateConfig()).toMatchSnapshot({ time: expect.any(Date) }) })内联快照-不会生成新的快照文件,而是将快照内容写入当前执行测试的文件中
其中在生成内联快照的时候需要第三方的包 prettier 配合
test('test config snapshot', () => { expect(generateConfig()).toMatchInlineSnapshot({ time: expect.any(Date), // 内联快照会放在这里 }) })总结:快照测试常用于配置文件的测试、React、Vue组件等的测试
在 Jest基础入门 中实现过接口请求的模拟,如下:
import axios from 'axios' import { getData } from './api' jest.mock('axios') test.only('test axios getData', async () => { // mock 改变函数的内部实现 axios.get.mockResolvedValue({ data: 'hello' }) await getData().then((response) => { expect(response).toBe('hello') }) })这种方法是通过改变 axios 内部方法实现来进行的模拟,这样所有的 get 请求都会被拦截到
另一种方式是对 getData 方法的 mock
jest.mock('./api') import { getData } from './api' test.only('test axios getData', async () => { await getData().then((response) => { expect(response).toBe('hello') }) })通过mock('./api') 这个文件,在下文导入 getData 这个方法的时候,不再从 './api' 文件中导入,而是去 __mocks__ 文件夹下对应的 api 文件中查找并导入,通过这种方式实现对 getData 方法的拦截处理
在实际的开发中,无需手动进行 unmock 操作,只需在 jest.config.js 中打开 clearMocks 选项即可,该选项会自动清理 mock 调用
什么是局部mock,即只针对一个文件中的部分内容进行mock,另一部分保留原始内容,不进行模拟
比如:
// util.js import axios from 'axios' export const getConfig = () => { return axios.get('/config') } export const getNumber => () => 12345在编写测试用例的时候,只想对 getConfig 方法进行mock,getNumber 使用真实值,不进行mock
这时就用上了 jest.requireActual 方法
jest.mock('./util') import { getConfig } from './util' const { getNumber } = jest.requireActual('./util') test('test axios getConfig', async () => { // 从 __mocks__下 util 文件中查找 await getConfig().then((response) => { expect(response).toMatchObject({ success: true }) }) }) test('test getNumber', () => { // 使用真实函数 expect(getNumber()).toEqual(12345) })对定时器进行测试
const timerFunc = (callback) => { setTimeout(() => { callback() }, 3000) } test('test timer', (done) => { timerFunc(() => { expect(1).toBe(1) done() }) })如果定时器时间过长,则测试时间也会很长,那么如何mock timer呢
// mock timer jest.useFakeTimers() test('test timer', () => { timerFunc(() => { const fn = jest.fn() timer(fn) // 立即执行全部定时器 jest.runAllTimers() // jest.runOnlyPendingTimers() 只运行处于已加入队列中的定时器 expect(fn).toHaveBeenCalledTimes(1) }) })当不知道该使用 runAllTimers 还是 runOnlyPendingTimers时,可以使用一个API进行替代,就是 advanceTimersByTime,其可以快进指定的时间,来让定时器提前执行,比如
// 定时器快进 3s 执行 jest.advanceTimersByTime(3000)为了保证每一个测试用例 timer 相互隔离,可以将 useFakeTimers 提至 beforeEach 钩子函数中
beforeEach(() => { jest.useFakeTimers() })对 class 中负责逻辑的测试
// util.js class Util { init() { } complex() { } } export default Util实例化 Util
let util beforeAll(() => { util = new Util() })实际开发中一般对 Util 进行 mock
// jest mock 发现util是一个类,会自动把类的构造函数和方法变成 jest.fn() jest.mock('./util') import Util from './util' let util beforeAll(() => { util = new Util() }) test('test Util', () => { expect(Util).toHaveBeenCalled() util.complex('complex') expect(Util.mock.instances[0].complex).toHaveBeenCalled() })也可以采用自定义mock,在 __mocks__ 下创建 util.js
const Util = jest.fn(() => { console.log('constructor') }) Util.prototype.complex = ject.fn(() => { console.log('complex') }) export default Util自定义mock的另一种方式
jest.mock('./util', () => { const Util = jest.fn(() => { console.log('constructor') }) Util.prototype.complex = ject.fn(() => { console.log('complex') }) return Util })这里推荐在 __mocks__ 文件夹下进行自定义mock
创建DOM节点
import $ from 'jquery' const addDivToBody = () => { $('body').append('<div/>') } test('test dom', () => { addDivToBody() expect($('body').find('div').length).toBe(1) })Nodejs不具备dom,jest在node环境中自己模拟了一套dom环境 jsDom