实例

模拟js库

  1. import { commonFun } from '@/assets/js/utils/utils.js';
  2. // 显示模拟commonFun文件里面的deepClone方法
  3. jest.mock('@/assets/js/utils/utils.js', () => {
  4. return {
  5. commonFun: {
  6. deepClone: jest.fn().mockImplementation((params) => {
  7. return params;
  8. })
  9. }
  10. }
  11. })

模拟一个axios

  1. let mockAxios = jest.mock('axios');
  2. // let mockAxios = jest.genMockFromModule('axios');
  3. mockAxios.create = jest.fn(() => mockAxios);
  4. mockAxios.get = jest.fn(() => Promise.resolve({ data: {} }));
  5. mockAxios.post = jest.fn(() => Promise.resolve({ data: {} }));
  6. mockAxios.put = jest.fn(() => Promise.resolve({ data: {} }));
  7. mockAxios.delete = jest.fn(() => Promise.resolve({ data: {} }));
  8. mockAxios.all = jest.fn(() => Promise.resolve());
  9. 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方法

  1. const mockFn = jest.fn();
  2. wrapper.vm.$on('input', mockFn);

模拟自定义方法

  1. it('点击组件,触发远程搜索方法', async() => {
  2. wrapper.vm.remoteMethod = jest.fn();
  3. await wrapper.find('.el-select').trigger('click');
  4. expect(wrapper.find('.el-input').classes()).toContain('is-focus');
  5. expect(wrapper.vm.remoteMethod).toBeCalled();
  6. });

模拟第三方库–require

  1. // 获取下载文件url方法
  2. let OSS = jest.fn().mockImplementation(() => {
  3. return {
  4. multipartUpload,
  5. signatureUrl
  6. }
  7. });
  8. module.exports = OSS;
  9. //test.spec.js
  10. const OSS = require('ali-oss');
  11. jest.mock('ali-oss');

模拟挂载到vue实例上–$message

  1. const Message = jest.fn();
  2. Message.warning = jest.fn();
  3. localVue.prototype.$message = Message;
  4. wrapper = mount(UploadFiles, {
  5. mocks: {
  6. createOssClient: jest.spyOn(localVue.prototype, 'createOssClient'),
  7. ossUploadFile: jest.fn().mockImplementationOnce(() => {
  8. return Promise.reject('failed');
  9. }).mockImplementationOnce(() => {
  10. return Promise.resolve('success');
  11. }),
  12. $message: Message
  13. }
  14. });

模拟window.location

  1. beforeAll(() => {
  2. // 因为单元测试不走main.js,所以需要手动注册upload函数
  3. upload.install(localVue);
  4. // 单独模拟window.location对象
  5. const windowLocation = JSON.stringify(window.location);
  6. delete window.location;
  7. Object.defineProperty(window, 'location', {
  8. value: JSON.parse(windowLocation)
  9. });
  10. });
  11. afterAll(() => {
  12. window.location = location;
  13. });

模拟console

  1. const originConsole = console.log;
  2. console.log = jest.fn();
  3. // ...
  4. expect(console.log).toHaveBeenCalled();
  5. // 恢复console
  6. console.log = originConsole;

模拟定时器

  1. // vue文件
  2. // 移除iframe
  3. setTimeout(() => {
  4. iframe.remove();
  5. }, 5 * 60 * 1000);
  6. // test文件
  7. jest.useFakeTimers();
  8. jest.runAllTimers();

挂载到全局—this.XXX

  1. wrapper = mount(UploadFiles, {
  2. mocks: {
  3. createOssClient: jest.spyOn(localVue.prototype, 'createOssClient'),
  4. ossUploadFile: jest.fn().mockImplementationOnce(() => {
  5. return Promise.reject('failed');
  6. }).mockImplementationOnce(() => {
  7. return Promise.resolve('success');
  8. }),
  9. $message: Message
  10. }
  11. });

模拟vuex

  1. wrapper = mount(PageHeader, {
  2. mocks: {
  3. $store: {
  4. dispatch: jest.fn()
  5. }
  6. }
  7. });

模拟时间

  1. let Date1 = new Date('2020-10-10 12:00:00').getTime()
  2. MockDate.set(Date1);
  3. func.setStorage("storage", 'storage1');
  4. // test
  5. MockDate.set('2000-11-22');
  6. new Date().toString()

only

  1. it.only('test', () => {})

坑点

reject

如果某个promise返回的是reject,我们的单元测试也需要加上trycatch

  1. try {
  2. await localVue.prototype.ossUploadFileFromOss();
  3. } catch (error) {
  4. expect(error).toEqual('没有要上传的文件');
  5. }

mock清除

每一次mock之后,为了不影响其他用例,最好将mock重置或者清空一下

  1. mockFn.mockClear();

注意:
清除mock只是把调用次数清空,但是返回的内容,仍旧没有清空,目前没有找到解决办法,只能每次模拟返回值都在当前作用域下

main.js

vue项目中,单元测试不会走main.js,所以如果需要模拟在main.js挂载的组件,需要手动执行一下

  1. import upload from '@/assets/js/upload/upload.js';
  2. beforeAll(() => {
  3. // 因为单元测试不走main.js,所以需要手动注册upload函数
  4. upload.install(localVue);
  5. });

click问题

无法检查@click方法调用,如下,单元测试不通过,但是在方法里面打印,发现是正常执行了

  1. // vue文件
  2. <div @click="ossDownloadFile">
  3. <slot name="trigger"></slot>
  4. </div>
  5. // test文件
  6. wrapper.vm.ossDownloadFile = jest.fn();
  7. await triggerDom.trigger('click');
  8. await wrapper.vm.$nextTick();
  9. expect(wrapper.vm.ossDownloadFile).toHaveBeenCalled();

这种情况出现在:绑定trigger绑定click事件的dom,绑定的方法无法成功
解决:如果给click事件绑定的时候添加一个修饰符.stop,则可以正常触发,已证实是可以的
其实不应该测试点击事件是否触发,因为这是vue自带的功能,不用测试,只需要测试方法里面的代码即可

注意不要测试其他框架的功能(element-ui、vue)

  1. @click="XXX"
  2. @change="XXX"
  3. :clear="XXX"