文章目录
1. 背景
本文将介绍关于jest
(单元测试)进阶的使用方法。
1.1 前置资料
2. mock
2.1 初识 mock
2.1.1 目的
- 捕获函数调用情况
- 设置函数返回值
- 改变函数的内部实现
2.1.2 jest.fn
jest.fn(implementation)
是创建 Mock 函数最简单的方式,如果没有定义函数内部的实现,jest.fn()
会返回undefined
作为返回值。详情请查看这里。
describe('test', () => {
beforeEach(() => {})
afterEach(() => {})
beforeAll(() => {})
afterAll(() => {})
test('jest.fn', async () => {
const callback = jest.fn()
callback()
expect(callback).toHaveBeenCalled()
}, 5000)
test('jest.fn default result', async () => {
const callback = jest.fn()
const result = callback()
expect(result).toBeUndefined()
}, 5000)
test('jest.fn custom result', async () => {
const callback = jest.fn(() => 123)
const result = callback()
expect(result).toBe(123)
}, 5000)
test('jest.fn mockReturnValue custom result', async () => {
const callback = jest.fn().mockReturnValue(123)
const result = callback()
expect(result).toBe(123)
}, 5000)
test('jest.fn mockReturnValueOnce custom result', async () => {
const callback = jest.fn()
callback.mockReturnValueOnce(123)
callback()
const result = callback()
expect(result).toBeUndefined()
}, 5000)
})
2.1.3 jest.mock
当我们使用类似 axios 之类的库去请求 restful 数据时,在写单元测试的时候并不需要进行实际的请求,此时使用jest.mock(moduleName, factory, options)
来伪造整个模块是十分有必要的。详情请查看这里。
2.1.4 jest.spyOn
有时候我们既想使用jest
捕获函数的调用情况,又想正常的执行被 spy 的函数。此时就可以用使用jest.spyOn(object, methodName)
。详情请查看这里。
2.1.5 jest.createMockFromModule
给定模块的名称,使用自动模拟系统生成模块的模拟版本。jest.genMockFromModule(moduleName)
是它的别名。详情请查看这里。
2.2 使用示例
2.2.1 child_process
在调用 child_process 模块时,有可能是非常耗时的操作或者是启动某个服务,此时如果不伪造child_process
模块,有可能导致 Functions、Statements 无法覆盖的问题,因此此处提供伪造child_process
模块的思路,以供读者参考。
// __mocks__/child-process.js
jest.mock('child_process', () => {
return {
exec (command, options, callback) {
const EventEmitter = require('events')
const stdout = new EventEmitter()
const stderr = new EventEmitter()
stdout.emit('data', 'stdout')
stderr.emit('data', 'stderr')
const child = new EventEmitter()
child.stdout = stdout
child.stderr = stderr
setTimeout(callback, 500)
setTimeout(() => child.emit('close', 'process closed'), 1000)
return child
},
spawn (command, args, options) {
const EventEmitter = require('events')
const stdout = new EventEmitter()
const stderr = new EventEmitter()
stdout.emit('data', 'stdout')
stderr.emit('data', 'stderr')
const child = new EventEmitter()
child.stdout = stdout
child.stderr = stderr
setTimeout(() => child.emit('close', 'process closed'), 1000)
return child
},
}
})
// __tests__/child-process.test.js
require('@__mocks__/child-process')
const { exec, spawn } = require('child_process')
describe('child-process.', () => {
beforeEach(() => {})
afterEach(() => {})
beforeAll(() => {})
afterAll(() => {})
test('exec callback', done => {
const callback = jest.fn()
exec('ls -1l', {}, () => {
callback()
expect(callback.mock.calls.length).toBe(1)
done()
})
}, 5000)
test('exec close', done => {
const onClose = jest.fn()
const child = exec('ls -1l', {}, () => {})
child.on('close', () => {
onClose()
expect(onClose.mock.calls.length).toBe(1)
done()
})
}, 5000)
test('spawn close', done => {
const onClose = jest.fn()
const child = spawn('ls', ['-1l', '.'], {})
child.on('close', () => {
onClose()
expect(onClose.mock.calls.length).toBe(1)
done()
})
}, 5000)
})
2.2.2 sessionStorage
/localStorage
本文用 nodejs 来演示sessionStorage
/localStorage
的测试,前端环境的 mock 方式与本文介绍的方法思路相同,在本文的基础上将 commonjs 的语法换成 es 的语法、将 window 对象的初始化去除即可。
// __mocks__/storage-data.js
module.exports = {
storage_test: 'storage_test_value',
}
// __mocks__/storage.js
const StorageData = require('@__mocks__/storage-data')
const { merge } = require('lodash')
module.exports = class MockStorage {
constructor () {
this.store = merge({}, StorageData)
this._updateProperties()
}
_updateProperties () {
this.keys = Object.keys(this.store)
this.length = this.keys.length
}
key (index) {
return this.keys[index]
}
clear () {
this.store = {}
this._updateProperties()
}
getItem (key) {
return this.store[key] || null
}
setItem (key, value) {
this.store[key] = value.toString()
}
removeItem (key) {
delete this.store[key]
}
}
// __mocks__/local-storage.js
const MockStorage = require('@__mocks__/storage')
module.exports = class MockLocalStorage extends MockStorage {
constructor () {
super()
}
}
// __mocks__/session-storage.js
const MockStorage = require('@__mocks__/storage')
module.exports = class MockSessionStorage extends MockStorage {
constructor () {
super()
}
}
// __tests__/storage/local.test.js
const MockLocalStorage = require('@__mocks__/local-storage')
const window = {}
describe('localStorage', () => {
beforeEach(() => {
Object.defineProperty(window, 'localStorage', {
writable: true,
value: new MockLocalStorage(),
})
})
afterEach(() => {})
beforeAll(() => {})
afterAll(() => {})
test('key storage_test', async () => {
const result = window.localStorage.key(0)
expect(result).toBe('storage_test')
})
test('getItem storage_test', async () => {
const result = window.localStorage.getItem('storage_test')
expect(result).toBe('storage_test_value')
})
test('clear', async () => {
window.localStorage.clear()
const result = window.localStorage.key(0)
expect(result).toBeUndefined()
})
test('setItem value_for_test', async () => {
const newVal = 'value_for_test'
window.localStorage.setItem('storage_test', newVal)
const result = window.localStorage.getItem('storage_test')
expect(result).toBe(newVal)
})
})
// __tests__/storage/session.test.js
const MockSessionStorage = require('@__mocks__/session-storage')
const window = {}
describe('sessionStorage', () => {
beforeEach(() => {
Object.defineProperty(window, 'sessionStorage', {
writable: true,
value: new MockSessionStorage(),
})
})
afterEach(() => {})
beforeAll(() => {})
afterAll(() => {})
test('key storage_test', async () => {
const result = window.sessionStorage.key(0)
expect(result).toBe('storage_test')
})
test('getItem storage_test', async () => {
const result = window.sessionStorage.getItem('storage_test')
expect(result).toBe('storage_test_value')
})
test('clear', async () => {
window.sessionStorage.clear()
const result = window.sessionStorage.key(0)
expect(result).toBeUndefined()
})
test('setItem value_for_test', async () => {
const newVal = 'value_for_test'
window.sessionStorage.setItem('storage_test', newVal)
const result = window.sessionStorage.getItem('storage_test')
expect(result).toBe(newVal)
})
})