实例
模拟js库
import { commonFun } from '@/assets/js/utils/utils.js';// 显示模拟commonFun文件里面的deepClone方法jest.mock('@/assets/js/utils/utils.js', () => {return {commonFun: {deepClone: jest.fn().mockImplementation((params) => {return params;})}}})
模拟一个axios
let mockAxios = jest.mock('axios');// let mockAxios = jest.genMockFromModule('axios');mockAxios.create = jest.fn(() => mockAxios);mockAxios.get = jest.fn(() => Promise.resolve({ data: {} }));mockAxios.post = jest.fn(() => Promise.resolve({ data: {} }));mockAxios.put = jest.fn(() => Promise.resolve({ data: {} }));mockAxios.delete = jest.fn(() => Promise.resolve({ data: {} }));mockAxios.all = jest.fn(() => Promise.resolve());export default mockAxios;
jest.genMockFromModule(moduleName)和jest.mock(moduleName, factory, options)对比:
| 描述 | jest.genMockFromModule | jest.mock |
|---|---|---|
| 模拟内容 | 自动模拟 | |
| 形式 | 对于函数,该方法会创建模拟函数(该函数没有形参,且返回值也是 undefined)(该方法对 async 函数也有效);对于类,该方法会创建新的类, 且保留原有接口;对于对象,该方法会以深拷贝的方式模拟该对象;对于数组,该方法会创建一个与原数组长度相同、但元素为空值的数组; 对于 string, number, bigint, boolean, null, undefined, symbol 原始类型,该方法会创建相同的模拟值 大概像是拷贝了一份 |
需要使用者手动添加模拟函数内容 |
模拟emit方法
const mockFn = jest.fn();wrapper.vm.$on('input', mockFn);
模拟自定义方法
it('点击组件,触发远程搜索方法', async() => {wrapper.vm.remoteMethod = jest.fn();await wrapper.find('.el-select').trigger('click');expect(wrapper.find('.el-input').classes()).toContain('is-focus');expect(wrapper.vm.remoteMethod).toBeCalled();});
模拟第三方库–require
// 获取下载文件url方法let OSS = jest.fn().mockImplementation(() => {return {multipartUpload,signatureUrl}});module.exports = OSS;//test.spec.jsconst OSS = require('ali-oss');jest.mock('ali-oss');
模拟挂载到vue实例上–$message
const Message = jest.fn();Message.warning = jest.fn();localVue.prototype.$message = Message;wrapper = mount(UploadFiles, {mocks: {createOssClient: jest.spyOn(localVue.prototype, 'createOssClient'),ossUploadFile: jest.fn().mockImplementationOnce(() => {return Promise.reject('failed');}).mockImplementationOnce(() => {return Promise.resolve('success');}),$message: Message}});
模拟window.location
beforeAll(() => {// 因为单元测试不走main.js,所以需要手动注册upload函数upload.install(localVue);// 单独模拟window.location对象const windowLocation = JSON.stringify(window.location);delete window.location;Object.defineProperty(window, 'location', {value: JSON.parse(windowLocation)});});afterAll(() => {window.location = location;});
模拟console
const originConsole = console.log;console.log = jest.fn();// ...expect(console.log).toHaveBeenCalled();// 恢复consoleconsole.log = originConsole;
模拟定时器
// vue文件// 移除iframesetTimeout(() => {iframe.remove();}, 5 * 60 * 1000);// test文件jest.useFakeTimers();jest.runAllTimers();
挂载到全局—this.XXX
wrapper = mount(UploadFiles, {mocks: {createOssClient: jest.spyOn(localVue.prototype, 'createOssClient'),ossUploadFile: jest.fn().mockImplementationOnce(() => {return Promise.reject('failed');}).mockImplementationOnce(() => {return Promise.resolve('success');}),$message: Message}});
模拟vuex
wrapper = mount(PageHeader, {mocks: {$store: {dispatch: jest.fn()}}});
模拟时间
let Date1 = new Date('2020-10-10 12:00:00').getTime()MockDate.set(Date1);func.setStorage("storage", 'storage1');// testMockDate.set('2000-11-22');new Date().toString()
only
it.only('test', () => {})
坑点
reject
如果某个promise返回的是reject,我们的单元测试也需要加上trycatch
try {await localVue.prototype.ossUploadFileFromOss();} catch (error) {expect(error).toEqual('没有要上传的文件');}
mock清除
每一次mock之后,为了不影响其他用例,最好将mock重置或者清空一下
mockFn.mockClear();
注意:
清除mock只是把调用次数清空,但是返回的内容,仍旧没有清空,目前没有找到解决办法,只能每次模拟返回值都在当前作用域下
main.js
vue项目中,单元测试不会走main.js,所以如果需要模拟在main.js挂载的组件,需要手动执行一下
import upload from '@/assets/js/upload/upload.js';beforeAll(() => {// 因为单元测试不走main.js,所以需要手动注册upload函数upload.install(localVue);});
click问题
无法检查@click方法调用,如下,单元测试不通过,但是在方法里面打印,发现是正常执行了
// vue文件<div @click="ossDownloadFile"><slot name="trigger"></slot></div>// test文件wrapper.vm.ossDownloadFile = jest.fn();await triggerDom.trigger('click');await wrapper.vm.$nextTick();expect(wrapper.vm.ossDownloadFile).toHaveBeenCalled();
这种情况出现在:绑定trigger绑定click事件的dom,绑定的方法无法成功
解决:如果给click事件绑定的时候添加一个修饰符.stop,则可以正常触发,已证实是可以的
其实不应该测试点击事件是否触发,因为这是vue自带的功能,不用测试,只需要测试方法里面的代码即可
注意不要测试其他框架的功能(element-ui、vue)
@click="XXX"@change="XXX":clear="XXX"
