Jest基本使用

339 阅读5分钟

基本断言:

官方文档:Expect · Jest中文文档 | Jest中文网

前言:在修改AI生成单测代码报错时,许多同类型错误都是对断言类型的使用不准确所导致的,因此了解几个常见断言之间的异同和针对的场景有利于我们在编写单测时更加迅速和准确

  • toBetoBe使用 Object.is来进行精准匹配的测试。要求绝对相等;

    • 当其为基础类型时则要求类型和值都相等,相当于===
    • expect(1).tobe('1'); // false 类型不同
      expect(1).tobe(1);   // true  类型和值都相同
      
    • 当其为引用类型时则要求指向同一内存地址
    • expect({ name: 'john doe' }).toBe({ name: 'john doe'});    
      // false因为对象字面量语法会创建基础对象的新实例。
      
      let a = { name: 'john doe' };
      let b = a;
      expect(a).toBe(b); // true ab指向同一地址
      
  • toStrictEqual :较tobe宽松一点,除了允许引用类型可以不指向同一地址外其余和tobe相同,主要用于遍历比较比较复杂的对象和数组,确保它们的属性、值以及其顺序完全相等,包括underfined和null也不允许

    • 对象除了内存地址指向不同其余都相同

    • expect({ name: 'john doe' }).toBe({ name: 'john doe'});  // true
      
    • 对象的属性和值都相同,顺序不同

    • expect({ name: 'john doe', sex: 'male' }).toBe({ sex: 'male', name: 'john doe'});  
      // false 顺序不同
      
    • 针对undefined、null这种属性值,toStrictEqual会严格对比每个属性和属性值,测试不会通过

    • expect({ name: 'john doe', sex: underfined }).toBe({ name: 'john doe'});  
      // false 不允许underfined
      
    • 数组缺省值也不通过

    • expect([, 1]).toBe([undefined, 1]);  
      // false 不允许数组缺省
      
  • toEqual :较toStrictEqual更宽松,允许顺序及基础类型不相同,主要用于检查两个对象是否具有相同的值,该匹配器递归地检查所有字段的相等性,而不是检查对象身份——这也称为“深度相等”

    • 对象的属性和值都相同,顺序不同
    • expect({ name: 'john doe', sex: 'male' }).toBe({ sex: 'male', name: 'john doe'});  
      // true
      

其他断言:

  • toBeNull :判断是否为null

  • toBeUndefined :判断是否undefined

  • toBeDefined : 判断是否被定义,undefined的反义词

  • toBeTruthy :判断是否true,会进行类型转换

  • toBeFalsy :判断匹配是否false,会进行类型转换

  • toBeGreaterThan :大于

  • toBeGreaterThanOrEqual :大于或等于

  • toBeLessThan :小于

  • toBeLessThanOrEqual:小于或等于

  • toBeCloseTo:可以用来比较浮点数,指定位数

  • toMatch(val) : 匹配字符串是否包含val

  • toContain(val) : 匹配数组是否包含val

  • toThrow : 匹配异常,如果传参则参数和抛出异常要一致才通过

Jest.Mock

官方文档:Mock Functions · Jest

前言:在项目中,一个模块的方法内常常会去调用另外一个模块的方法。在单元测试中,我们可能并不需要关心内部调用的方法的执行过程和结果,只想知道它是否被正确调用即可,甚至会指定该函数的返回值。此时,使用Mock函数是十分有必要。

Mock函数提供的以下三种特性:

  • 捕获函数调用情况
  • 设置函数返回值
  • 改变函数的内部实现

jest.fn()

jest.fn()是创建Mock函数最简单的方式,如果没有定义函数内部的实现,jest.fn()会返回undefined作为返回值。

test('测试jest.fn()调用', () => {
  let mockFn = jest.fn();
  let result = mockFn(1, 2, 3);

  // 断言mockFn的执行后返回undefined
  expect(result).toBeUndefined();
  // 断言mockFn被调用
  expect(mockFn).toBeCalled();
  // 断言mockFn被调用了一次
  expect(mockFn).toBeCalledTimes(1);
  // 断言mockFn传入的参数为1, 2, 3
  expect(mockFn).toHaveBeenCalledWith(1, 2, 3);
})

jest.fn()所创建的Mock函数还可以设置返回值,定义内部实现或返回Promise对象。

// functions.test.js

test('测试jest.fn()返回固定值', () => {
  let mockFn = jest.fn().mockReturnValue('default');
  // 断言mockFn执行后返回值为default
  expect(mockFn()).toBe('default');
})

test('测试jest.fn()内部实现', () => {
  let mockFn = jest.fn((num1, num2) => {
    return num1 * num2;
  })
  // 断言mockFn执行后返回100
  expect(mockFn(10, 10)).toBe(100);
})

test('测试jest.fn()返回Promise', async () => {
  let mockFn = jest.fn().mockResolvedValue('default');
  let result = await mockFn();
  // 断言mockFn通过await关键字执行后返回值为default
  expect(result).toBe('default');
  // 断言mockFn调用后返回的是Promise对象
  expect(Object.prototype.toString.call(mockFn())).toBe("[object Promise]");
})

jest.mock()

已经封装的请求方法可能我们在其他模块被调用的时候,并不需要进行实际的请求(请求方法已经通过单测或需要该方法返回非真实数据)。此时,使用jest.mock()去mock整个模块是十分有必要的。

// fetch.js

import axios from 'axios';

export default {
  async fetchPostsList(callback) {
    return axios.get('https://jsonplaceholder.typicode.com/posts').then(res => {
      return callback(res.data);
    })
  }
}
// events.js
import fetch from './fetch';

export default {
  async getPostList() {
    return fetch.fetchPostsList(data => {
      console.log('fetchPostsList be called!');
    });
  }
}
// functions.test.js

import events from '../src/events';
import fetch from '../src/fetch';

jest.mock('../src/fetch.js');

test('mock 整个 fetch.js模块', async () => {
  expect.assertions(2);
  await events.getPostList();
  expect(fetch.fetchPostsList).toHaveBeenCalled();
  expect(fetch.fetchPostsList).toHaveBeenCalledTimes(1);
});

在jest中如果想捕获函数的调用情况,则该函数必须被mock或者spy

jest.spyOn()

jest.spyOn()方法同样创建一个mock函数,但是该mock函数不仅能够捕获函数的调用情况,还可以正常的执行被spy的函数。实际上,jest.spyOn()是jest.fn()的语法糖,它创建了一个和被spy的函数具有相同内部代码的mock函数。

console.log('fetchPostsList be called!');这行代码并没有被打印,这是因为通过jest.mock()后,模块内的方法是不会被jest所实际执行的。这时我们就需要使用jest.spyOn()。

// functions.test.js

import events from '../src/events';
import fetch from '../src/fetch';

test('使用jest.spyOn()监控fetch.fetchPostsList被正常调用', async() => {
  expect.assertions(2);
  const spyFn = jest.spyOn(fetch, 'fetchPostsList');
  await events.getPostList();
  expect(spyFn).toHaveBeenCalled();
  expect(spyFn).toHaveBeenCalledTimes(1);
})

总结

在实际项目的单元测试中,jest.fn()常被用来进行某些有回调函数的测试;jest.mock()可以mock整个模块中的方法,当某个模块已经被单元测试100%覆盖时,使用jest.mock()mock该模块,节约测试时间和测试的冗余度是十分必要;当需要测试某些必须被完整执行的方法时,常常需要使用jest.spyOn()