实例
模拟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.js
const 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();
// 恢复console
console.log = originConsole;
模拟定时器
// vue文件
// 移除iframe
setTimeout(() => {
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');
// test
MockDate.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"