【测试一下!】vue3的单元测试(一)-jest基础篇

878 阅读4分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第1天,点击查看活动详情

vue3常用的几个测试框架

  • jest
  • mocha
  • vue-test-utils -官方工具

他们都包含几大功能

  • 断言
  • 异步支持
  • 代码覆盖率
  • 单元测试

我们从实践中看如何选择合适的测试方案 先从基础开始

本篇是jest基础,然后是vue-test-utils基础,最后一篇结合项目看一下怎么做项目中的实际测试

起步

安装jest和type 包(方便vscode 代码提示)

pnpm add jest --save-dev
pnpm add @types/jest --save

新建.test.js文件

//最简单的示例
it('test add', () => {
  expect(2 + 2).toBe(4);
});

执行jest

// 默认会搜索所有.test.js 的文件进行执行
npx jest // 没有全局安装的情况下,如果全局安装直接jest

npx jest src/a.test.js --watchAll //监听文件改动并执行

测试

测试异步

  1. 回调方法方式

执行回调后需要调用done方法

const fetchData = (cb) => {
  setTimeout(() => {
    cb('hello');
  }, 200);
};

it('test callBack', (done) => {
  fetchData((data) => {
    expect(data).toBe('hello');
    done();
  });
});
  1. promise方式
const usePromise = () => Promise.resolve('hello');

it('test promise', () => {
  return usePromise().then((data) => {
    expect(data).toBe('hello');
  });
});

//或者

it('test expect', () => {
  return expect(usePromise()).resolves.toBe('hello');
});
  1. async 方式
const usePromise = () => Promise.resolve('hello');

it('test async', async () => {
  const data = await usePromise();
  expect(data).toBe('hello');
});

mock

测试方法的执行

function mockTest(tickCallBack, cb) {
  if (tickCallBack) {
    cb(11);
  }
}


t('test mock fn', () => {
  const mockCb = jest.fn(); //定一个方法监听器
  mockTest(true, mockCb);
  expect(mockCb).toHaveBeenCalled(); // 测试函数是否被执行
  expect(mockCb).toHaveBeenCalledWith(11); // 测试函数的参数是否是1
  expect(mockCb).toHaveReturnedTimes(1); // 测试函数被调用的次数是否为1次
});

mock第三方库

比如我们想测试一个请求,查看请求返回和请求链接,当然使用的是我们封装的请求方式,但是又不想真实的发出请求,因为有很多的不确定因素,当然也不能修改原来封装的方法,所以我们可以直接mock axios 工具,然后模拟返回

前置工作

  1. 新建json文件然后安装json-server 启动一个json服务,也可以使用jsonplaceholder模拟一个远程json。
  2. 封装一个axios方式模拟,项目里用就直接用项目里的,这里是做测试
  3. 写测试用例
/* eslint-disable @typescript-eslint/no-var-requires */
const axios = require('axios');

function getTestData() {
  return axios.get('http://localhost:3000/userMsg').then((res) => {
    return res.data.data;
  });
}

it('test axios req', (done) => {
  getTestData().then((res) => {
    console.log(res.name);
    done();
  });
});

此时没有做任何处理,请求是真正会被发出去

mock axios

jest.mock('axios');
axios.get.mockResolvedValue({ data: { data: { name: '被mock后的数据' } } });

此时再打印返回,就可以看到,name已经被修改,真正的请求也不会发出

同样也可以测试,axios方法的执行

it('test axios req', (done) => {
  getTestData().then((res) => {
    console.log(res.name);
    expect(axios.get).toHaveBeenCalled();
    expect(axios.get).toHaveBeenCalledTimes(1);
    done();
  });
});

或者不想每个测试文件 都写一份,或者防止漏写,

可以在根目录下创建__mocks__ 文件,然后对包进行覆写

要和node_mnodules 同级,并且里面文件名要和引入包名一致

然后里面创建文件,比如创建axios包的覆写文件

const axios = {
  get: jest.fn(() => {
    return Promise.resolve({
      data: { data: { name: '__mocks__文件夹里面修改的数据' } },
    });
  }),
};

module.exports = axios;

然后.test.js里面删除对axios的mock

/* eslint-disable @typescript-eslint/no-var-requires */
const axios = require('axios');
// 不需要再mock 直接走__mocks__ 里面的覆写操作
// jest.mock('axios');
// axios.get.mockResolvedValue({ data: { data: { name: '被mock后的数据' } } });
function getTestData() {
  return axios.get('http://localhost:3000/userMsg').then((res) => {
    return res.data.data;
  });
}

it('test axios req', (done) => {
  getTestData().then((res) => {
    console.log(res.name);
    expect(axios.get).toHaveBeenCalled();
    expect(axios.get).toHaveBeenCalledTimes(1);
    done();
  });
});

测试Timer

对setTimeout 和setInvertal的测试

常用的API:

  • jest.useFakeTimers(); // 对时间进行代理
  • jest.runAllTimers(); 所有时间执行结束后执行后续
  • jest.runOnlyPendingTimers(); //执行一次时间器后执行
  • jest.advanceTimersByTime(2000); // 根据时间细粒度控制
  1. 单次
const fetchData = (cb) => {
  setTimeout(() => {
    cb('hello');
  }, 1000);
};

jest.useFakeTimers();
it('test', () => {
  const cbFn = jest.fn();
  fetchData(cbFn);
  jest.runAllTimers();
  expect(cbFn).toHaveBeenCalledTimes(1);
});
  1. 多次
const secondTimeOut = (cb) => {
  setTimeout(() => {
    cb('one');
    setTimeout(() => {
      cb('second');
    }, 1000);
  }, 2000);
};


it('test second timeOut', () => {
  const cbFn = jest.fn();
  secondTimeOut(cbFn);
  jest.runOnlyPendingTimers();// 第一次时间器执行后
  expect(cbFn).toHaveBeenCalled();
  expect(cbFn).toHaveBeenCalledTimes(1);
  jest.runOnlyPendingTimers();//第二次时间器执行后
  expect(cbFn).toHaveBeenCalledTimes(2);

});
  1. 按照时间控制
it('test second time advance', () => {
  const cbFn = jest.fn();
  secondTimeOut(cbFn);
  jest.advanceTimersByTime(2000);// 两秒后往后执行
  expect(cbFn).toHaveBeenCalled();
  expect(cbFn).toHaveBeenCalledTimes(1);
  jest.advanceTimersByTime(1000); //再过一秒后执行
  expect(cbFn).toHaveBeenCalledTimes(2);
});