Jest测试框架模拟函数mock

8,915 阅读18分钟

入门部分

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

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

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对象

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]");
  //上面这个实际上返回的是String对象,返回Promise对象的写法要怎么做呢?
})

下面我们在src/fetch.js文件中写一些被测试代码,以更加接近业务的方式来理解Mock函数的实际应用。

被测试代码中依赖了axios这个常用的请求库和JSONPlaceholder请求接口,请先在shell中执行npm install axios --save安装依赖,。

// fetch.js
const axios = require('axios');

async function fetchPostsList(callback) {
    return axios.get('https://jsonplaceholder.typicode.com/posts').then(res => {
        return callback(res.data);
    })
}

module.exports = fetchPostsList;

我们在fetch.js中封装了一个fetchPostsList方法,该方法请求了JSONPlaceholder提供的接口,并通过传入的回调函数返回处理过的返回值。如果我们想测试该接口能够被正常请求,只需要捕获到传入的回调函数能够被正常的调用即可。下面是functions.test.js中的测试的代码。

const fetch = require('../src/fetch');
test('fetchPostsList callback was called', async () => {
    expect.assertions(1);
    let mockFn = jest.fn();
    await fetch(mockFn);

    // 断言mockFn被调用
    expect(mockFn).toBeCalled();
})

.mock属性

所有的 mock 函数都有一个特殊的 .mock 属性,它保存了关于此函数如何被调用、调用时的返回值的信息。

test('test function forEach', () => {
    const mockCallback = jest.fn(x => 88 + x);
    forEach([0, 1], mockCallback);
    // mock函数被调用的次数
    expect(mockCallback.mock.calls.length).toBe(2);
    // 第一次调用函数时的第一个参数是0
    expect(mockCallback.mock.calls[0][0]).toBe(0);
    // 第一次调用函数时第二个参数时mockCallback
    // expect(mockCallback.mock.calls[0][1]).toBe(1);???
    // 第二次调用函数时第一个参数是1
    expect(mockCallback.mock.calls[1][0]).toBe(1);
    // 第一次函数调用的返回值是88
    expect(mockCallback.mock.results[0].value).toBe(88);
    // 第二次函数调用的返回值是89
    expect(mockCallback.mock.results[1].value).toBe(89);
})

Jest.mock()

通常情况下,我们需要调用api,发送ajax请求,从后台获取数据。但是我们在做前端测试的时候,并不需要去调用真实的接口,所以此时我们需要模拟 axios/fetch 模块,让它不必调用api也能测试我们的接口调用是否正确。

// events.js
const fetch = require('./fetch');

async function getPostList() {
    return fetch(data => {
        console.log('fetchPostsLits be called');
    })
}

module.exports = getPostList;

测试代码如下:

const fetch = require('../src/fetch');
const event = require('../src/event');
jest.mock('../src/fetch.js');

test('fetchPostsList callback was called', async () => {
    expect.assertions(1);
    let mockFn = jest.fn();
    await fetch(mockFn);

    expect(mockFn).toBeCalled();
})
//这里会报错
/* Error: expect(received).toHaveBeenCalled()

Matcher error: received value must be a mock or spy function

Received has value: undefined

    at Object.<anonymous> (/Users/giriawu/Documents/test_demo/getting-started-with-jest/__tests__/functions.test.js:15:21)
    at processTicksAndRejections (internal/process/task_queues.js:93:5)
*/
//该如何解决?
test('mock all fetch.js', async () => {
    expect.assertions(2);
    await event();
    expect(fetch()).toHaveBeenCalled()
    expect(fetch()).toHaveBeenCalledTimes(1);
})

在测试代码中我们使用了 jest.mock('axios') 去mock整个 fetch.js 模块。如果注释掉这行代码,执行测试脚本时会出现报错

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

Jest.spyOn()

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

jest.spyOn() 方法创建一个mock函数,并且可以正常执行被spy的函数。 jest.spyOn()jest.fn() 的语法糖,它创建了一个和被spy的函数具有相同内部代码的mock函数。

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);
})

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

Timer Mock

jest可以模拟定时器从而允许自主控制时间流逝。模拟定时器运行可以方便测试,比如不必等待一个很长的延时而是直接获取结果。

jest对象上与timer mock相关的方法主要有以下个:

  1. jest.useFakeTimers():指示Jest使用标准计时器函数的假版本(setTimeout,setInterval,clearTimeout,clearInterval,nextTick,setImmediate和clearImmediate)。
  2. jest.useRealTimers():指示Jest使用标准计时器功能的真实版本。
  3. jest.clearAllTimers():从计时器系统中清除任何等待的计时器。
  4. jest.runAllTicks():执行微任务队列中的所有任务(通常通过process.nextTick在节点中连接)。
  5. jest.runAllTimers():执行宏任务队列中的所有任务。
  6. jest.runAllImmediates():通过setImmediate()执行任务队列中的所有任务。
  7. jest.advanceTimersByTime(n):执行宏任务队列中的所有任务,当该API被调用时,所有定时器被提前 n 秒。
  8. jest.runOnlyPendingTimers():仅执行宏任务中当前正在等待的任务。

举例来说(见Example.5):

// timer.js
function timerGame(callback) {
    console.log('Ready....go!');
    setTimeout(() => {
        console.log('Times up -- stop!');
        callback && callback();
    }, 1000);
}

module.exports = timerGame;

我们在timer.js中设定了一个1s钟之后才会执行的定时器,但是测试代码是同步执行,通过timer mock,我们不必等待定时器执行完成就可以完成测试。

const timer = require('./timer');
const callback = jest.fn();

jest.useFakeTimers();

test('calls the callback after 1 second', () => {
    timer(callback);

    expect(callback).not.toBeCalled();

    expect(setTimeout).toHaveBeenCalledTimes(1);
    expect(setInterval).toHaveBeenCalledTimes(0);
    expect(setTimeout).toHaveBeenLastCalledWith(expect.any(Function), 1000);

    jest.runAllTimers();

    expect(callback).toBeCalled();
    expect(callback).toHaveBeenCalledTimes(1);
});

在上面的代码中,我们已经在第四行声明使用假时间,在test块中,虽然setTimeout尚未执行完毕,但是测试已经完成,setTimeout执行一次,没有setInterval执行,这与期望一致。接下来调用jest.runAllTimers()使得所有定时器立即执行完毕,控制台打印定时器中的输出。

对于递归定时器的情况,如果使用jest.runAllTimers(),所有的定时器就无限循环了,这个时候就需要用到jest.runOnlyPendingTimers()了,因为runOnlyPendingTimers的过程中不会产生新的定时器,从而避免了无限循环的问题,如Example.5 中pendingTimer所示。

jest.advanceTimersByTime(n)也很容易理解,就是将所有定时器提前n秒执行。如下所示,在未调用jest.advanceTimersByTime(n)之前,callback还没有被调用,然后通过jest.advanceTimersByTime(1000)让定时器提前1s执行,因此接下来的断言不会报错。

// timer3.test.js
const timerGame = require('./timer');
const callback = jest.fn();
jest.useFakeTimers();

test('calls the callback after 1 second via advanceTimersByTime', () => {
    timerGame(callback);

    // At this point in time, the callback should not have been called yet
    expect(callback).not.toBeCalled();

    // Fast-forward until all timers have been executed
    jest.advanceTimersByTime(1000);

    // Now our callback should have been called!
    expect(callback).toBeCalled();
    expect(callback).toHaveBeenCalledTimes(1);
});

同样的,如果使用jest.advanceTimersByTime(500)提前0.5s,上面的测试可以进行如下修改。

jest.advanceTimersByTime(500);

expect(callback).not.toBeCalled();
expect(callback).toHaveBeenCalledTimes(0);

Manual Mock

Manual Mock用于存储模拟数据的功能。例如,您可能希望创建一个允许您使用虚假数据的手动模拟,而不是访问网站或数据库等远程资源。这可以确保您的测试快速且稳定。

通过在紧邻模块的__mocks __子目录中编写模块来定义手动模拟。例如,要在models目录中模拟一个名为user的模块(如Example.9 所示),请创建一个名为user.js的文件并将其放在models / __ mocks__目录中。同时请注意__mocks__文件夹区分大小写。

// userMocked.test.js
import user from './models/user';

jest.mock('./models/user');

test('if user model is mocked', () => {
    expect(user.getAuthenticated()).toEqual({age: 622, name: 'Mock name'});
});

如果您正在模拟的模块是Node模块(例如:lodash),则mock应放在与node_modules相邻的__mocks__目录中,并将自动模拟。没有必要显式调用jest.mock('module_name')。但是如果想模拟Node的核心模块(例如:fspath),那么明确地调用例如: jest.mock('path')是必需的,因为默认情况下不会模拟核心Node模块。

如果您正在使用ES6模块导入,那么您通常倾向于将导入语句放在测试文件的顶部。但是通常你需要指示Jest在模块使用它之前使用模拟。出于这个原因,Jest会自动将jest.mock调用提升到模块的顶部(在任何导入之前)。

ES6 Class Mock

如Example.6中所示,分别有SoundPlayer类和使用该类的SoundPlayerConsumer类。我们将在SoundPlayerConsumer的测试中模拟SoundPlayer

// sound-player.js
export default class SoundPlayer {
  constructor() {
    this.foo = 'bar';
  }

  playSoundFile(fileName) {
    console.log('Playing sound file ' + fileName);
  }
}
// sound-player-consumer.js
import SoundPlayer from './sound-player';

export default class SoundPlayerConsumer {
  constructor() {
    this.soundPlayer = new SoundPlayer();
  }

  playSomethingCool() {
    const coolSoundFileName = 'song.mp3';
    this.soundPlayer.playSoundFile(coolSoundFileName);
  }
}

分别有4种方法可以创建ES6类的模拟:

1.Automatic mock

使用该方法,类中的所有方法调用状态保存在theAutomaticMock.mock.instances[index].methodName.mock.calls中。同时,如果您在类中使用了箭头函数,它们将不会成为模拟的一部分。因为箭头函数不存在于对象的原型上,它们只是包含对函数的引用的属性。

注:由于Example.6中同时存在Automatic mock和Manual mock,所以在使用该方法时需要把__mocks__文件夹改个名。

2.Manual mock

在相邻的__mocks__文件夹下生成同名文件从而进行替换模拟。

注意:使用manual mock时确保__mocks__文件夹命名正确。

3.Calling jest.mock() with the module factory parameter

使用此种方法就是在jest.mock()中添加一个参数:jest.mock(path,moduleFactory)接受moduleFactory参数,moduleFactory返回一个模拟的函数。为了模拟构造函数,moduleFactory必须返回构造函数,换句话说,模块工厂必须是返回函数的函数。

4.Replacing the mock using mockImplementation() or mockImplementationOnce()

jest.mock的调用会被提升到代码的顶部。 您通过在现有mock上调用mockImplementation()(或mockImplementationOnce())而不是使用factory参数,从而稍后指定模拟(例如,在beforeAll()),或者在测试之间更改模拟。

Bypassing Module Mock

Jest允许您模拟测试中的整个模块,这有助于测试代码是否正确,函数调用是否正确。但是,有时您可能希望在测试文件中使用模拟模块的一部分,在这种情况下,您希望访问原始实现,而不是模拟版本。jest.requireActual()允许你导入实际的版本,而不是模拟的版本。

总结

通过mock函数我们可以通过以下三个特性去更好的编写我们的测试代码:

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

实际项目的单元测试中:

  • jest.fn() 常被用来进行某些有回调函数的测试;

  • jest.mock() 可以mock整个模块中的方法,当某个模块已经被单元测试100%覆盖时,使用jest.mock()去mock该模块,节约测试时间和测试的冗余度是十分必要;

  • jest.spyOn() 当需要测试某些必须被完整执行的方法时,常常需要使用;

这些都需要开发者根据实际的业务代码灵活选择。

官方文档部分

Mock 函数允许你测试代码之间的连接——实现方式包括:擦除函数的实际实现、捕获对函数的调用 ( 以及在这些调用中传递的参数) 、在使用 new 实例化时捕获构造函数的实例、允许测试时配置返回值。

有两种方法可以模拟函数:要么在测试代码中创建一个 mock 函数,要么编写一个手动 mock来覆盖模块依赖。

使用mock函数

假设我们要测试函数 forEach 的内部实现,这个函数为传入的数组中的每个元素调用一次回调函数。

function forEach(items, callback) {
  for (let index = 0; index < items.length; index++) {
    callback(items[index]);
  }
}

为了测试此函数,我们可以使用一个 mock 函数,然后检查 mock 函数的状态来确保回调函数如期调用。

const mockCallback = jest.fn(x => 42 + x);
forEach([0, 1], mockCallback);

// 此 mock 函数被调用了两次
expect(mockCallback.mock.calls.length).toBe(2);

// 第一次调用函数时的第一个参数是 0
expect(mockCallback.mock.calls[0][0]).toBe(0);

// 第二次调用函数时的第一个参数是 1
expect(mockCallback.mock.calls[1][0]).toBe(1);

// 第一次函数调用的返回值是 42
expect(mockCallback.mock.results[0].value).toBe(42);

.mock属性

所有的 mock 函数都有这个特殊的 .mock属性,它保存了关于此函数如何被调用、调用时的返回值的信息。 .mock 属性还追踪每次调用时 this的值,所以我们同样可以也检视(inspect) this

const myMock = jest.fn();

const a = new myMock();
const b = {};
const bound = myMock.bind(b);
bound();

console.log(myMock.mock.instances);
// > [ <a>, <b> ]

这些 mock 成员变量在测试中非常有用,用于说明这些 function 是如何被调用、实例化或返回的:

// 这个函数只调用一次
expect(someMockFunction.mock.calls.length).toBe(1);

// 这个函数被第一次调用时的第一个 arg 是 'first arg'
expect(someMockFunction.mock.calls[0][0]).toBe('first arg');

// 这个函数被第一次调用时的第二个 arg 是 'second arg'
expect(someMockFunction.mock.calls[0][1]).toBe('second arg');

// 这个函数被实例化两次
expect(someMockFunction.mock.instances.length).toBe(2);

// 这个函数被第一次实例化返回的对象中,有一个 name 属性,且被设置为了 'test’ 
expect(someMockFunction.mock.instances[0].name).toEqual('test');

Mock的返回值

Mock 函数也可以用于在测试期间将测试值注入代码︰

const myMock = jest.fn();
console.log(myMock());
// > undefined

myMock.mockReturnValueOnce(10).mockReturnValueOnce('x').mockReturnValue(true);

console.log(myMock(), myMock(), myMock(), myMock());
// > 10, 'x', true, true

在函数连续传递风格(functional continuation-passing style)的代码中时,Mock 函数也非常有效。 以这种代码风格有助于避免复杂的中间操作,便于直观表现组件的真实意图,这有利于在它们被调用之前,将值直接注入到测试中。

const filterTestFn = jest.fn();

// Make the mock return `true` for the first call,
// and `false` for the second call
filterTestFn.mockReturnValueOnce(true).mockReturnValueOnce(false);

const result = [11, 12].filter(num => filterTestFn(num));

console.log(result);
// > [11]
console.log(filterTestFn.mock.calls[0][0]); // 11
console.log(filterTestFn.mock.calls[1][0]); // 12

大多数现实世界例子中,实际是在依赖的组件上配一个模拟函数并配置它,但手法是相同的。 在这些情况下,尽量避免在非真正想要进行测试的任何函数内实现逻辑。

模拟模块

假定有个从 API 获取用户的类。 该类用 axios 调用 API 然后返回 data,其中包含所有用户的属性:

// users.js
import axios from 'axios';

class Users {
  static all() {
    return axios.get('/users.json').then(resp => resp.data);
  }
}

export default Users;

为测试该方法而不实际调用 API (使测试缓慢与脆弱),我们可以用 jest.mock(...) 函数自动模拟 axios 模块。

一旦模拟模块,我们可为 .get 提供一个 mockResolvedValue ,它会返回假数据用于测试。相当于,我们想要axios.get('/users.json')返回一个假的response

// users.test.js
import axios from 'axios';
import Users from './users';

jest.mock('axios');

test('should fetch users', () => {
  const users = [{name: 'Bob'}];
  const resp = {data: users};
  axios.get.mockResolvedValue(resp);

  // or you could use the following depending on your use case:
  // axios.get.mockImplementation(() => Promise.resolve(resp))

  return Users.all().then(data => expect(data).toEqual(users));
});

Mock实现

在某些情况下用Mock函数替换指定返回值是非常有用的。 可以用 jest.fnmockImplementationOnce方法来实现Mock函数。

const myMockFn = jest.fn(cb => cb(null, true));

myMockFn((err, val) => console.log(val));
// > true

当你需要根据别的模块定义默认的Mock函数实现时,mockImplementation 方法是非常有用的。

// foo.js
module.exports = function () {
  // some implementation;
};

// test.js
jest.mock('../foo'); // this happens automatically with automocking
const foo = require('../foo');

// foo is a mock function
foo.mockImplementation(() => 42);
foo();
// > 42

当你需要模拟某个函数调用返回不同结果时,请使用 mockImplementationOnce 方法︰

const myMockFn = jest
  .fn()
  .mockImplementationOnce(cb => cb(null, true))
  .mockImplementationOnce(cb => cb(null, false));

myMockFn((err, val) => console.log(val));
// > true

myMockFn((err, val) => console.log(val));
// > false

mockImplementationOne定义的实现逐个调用完毕时, 如果定义了jest.fn ,它将使用 jest.fn

const myMockFn = jest
  .fn(() => 'default')
  .mockImplementationOnce(() => 'first call')
  .mockImplementationOnce(() => 'second call');

console.log(myMockFn(), myMockFn(), myMockFn(), myMockFn());
// > 'first call', 'second call', 'default', 'default'

大多数情况下,我们的函数调用都是链式的,如果你希望创建的函数支持链式调用(因为返回了this),可以使用.mockReturnThis() 函数来支持。

const myObj = {
  myMethod: jest.fn().mockReturnThis(),
};

// is the same as

const otherObj = {
  myMethod: jest.fn(function () {
    return this;
  }),
};

Mock名称

  • .mockName()

你可以为你的Mock函数命名,该名字会替代 jest.fn() 在单元测试的错误输出中出现。 用这个方法你就可以在单元测试输出日志中快速找到你定义的Mock函数。

const myMockFn = jest
  .fn()
  .mockReturnValue('default')
  .mockImplementation(scalar => 42 + scalar)
  .mockName('add42');

自定义匹配器

测试Mock函数需要写大量的断言,为了减少代码量,我们提供了一些自定义匹配器。

// The mock function was called at least once
expect(mockFunc).toHaveBeenCalled();

// The mock function was called at least once with the specified args
expect(mockFunc).toHaveBeenCalledWith(arg1, arg2);

// The last call to the mock function was called with the specified args
expect(mockFunc).toHaveBeenLastCalledWith(arg1, arg2);

// All calls and the name of the mock is written as a snapshot
expect(mockFunc).toMatchSnapshot();

这些匹配器是断言Mock函数的语法糖。 你可以根据自己的需要自行选择匹配器。

// The mock function was called at least once
expect(mockFunc.mock.calls.length).toBeGreaterThan(0);

// The mock function was called at least once with the specified args
expect(mockFunc.mock.calls).toContainEqual([arg1, arg2]);

// The last call to the mock function was called with the specified args
expect(mockFunc.mock.calls[mockFunc.mock.calls.length - 1]).toEqual([
  arg1,
  arg2,
]);

// The first arg of the last call to the mock function was `42`
// (note that there is no sugar helper for this specific of an assertion)
expect(mockFunc.mock.calls[mockFunc.mock.calls.length - 1][0]).toBe(42);

// A snapshot will check that a mock was invoked the same number of times,
// in the same order, with the same arguments. 它还会在名称上断言。
expect(mockFunc.mock.calls).toEqual([[arg1, arg2]]);
expect(mockFunc.getMockName()).toBe('a mock name');

常用方法总结

Mock函数常用方法:

  1. mockFn.mockName(value):设置mock函数的名字

  2. mockFn.getMockName(): 返回mockFn.mockName(value)中设置的名字

  3. mockFn.mock.callsmock函数的调用信息

mockFn.mock.calls返回一个数组,数组中的每一个元素又是一个数组,包含mock函数的调用信息。比如,一个被调用两次的模拟函数f,参数为f('arg1','arg2'),然后使用参数f('arg3','arg4')mockFn.mock.calls返回的数组形式如下:

[[‘arg1’, ‘arg2’], [‘arg3’, ‘arg4’]]

因此,mockFn.mock.calls.length代表mock函数被调用次数,mockFn.mock.calls[0][0]代表第一次调用传入的第一个参数,以此类推。

  1. mockFn.mock.resultsmock函数的return值,以数组存储

  2. mockFn.mock.instancesmock函数实例

const mockFn = jest.fn();

const a = new mockFn();
const b = new mockFn();

mockFn.mock.instances[0] === a; // true
mockFn.mock.instances[1] === b; // true
  1. mockFn.mockImplementation(fn):创建一个mock函数

注意:jest.fn(implementation)jest.fn().mockImplementation(implementation)的简写。

  1. mockFn.mockImplementationOnce(fn):创建一个mock函数

该函数将用作对mocked函数的一次调用的mock的实现。可以链式调用,以便多个函数调用产生不同的结果。

const myMockFn = jest
  .fn()
  .mockImplementationOnce(cb => cb(null, true))
  .mockImplementationOnce(cb => cb(null, false));

myMockFn((err, val) => console.log(val)); // true

myMockFn((err, val) => console.log(val)); // false

mocked函数用完使用mockImplementationOnce定义的实现时,如果调用它们,它将使用jest.fn(()=> defaultValue).mockImplementation(()=> defaultValue)执行默认实现集:

const myMockFn = jest
  .fn(() => 'default')
  .mockImplementationOnce(() => 'first call')
  .mockImplementationOnce(() => 'second call');

// 'first call', 'second call', 'default', 'default'
console.log(myMockFn(), myMockFn(), myMockFn(), myMockFn());
  1. mockFn.mockReturnThis()jest.fn()的语法糖
jest.fn(function() {
  return this;
});
  1. mockFn.mockReturnValue(value):接受一个值作为调用mock函数时的返回值
const mock = jest.fn();
mock.mockReturnValue(42);
mock(); // 42
mock.mockReturnValue(43);
mock(); // 43
  1. mockFn.mockReturnValueOnce(value):接受一个值作为调用mock函数时的返回值,可以链式调用,以便产生不同的结果。

当不再使用mockReturnValueOnce值时,调用将返回mockReturnValue指定的值。

const myMockFn = jest
  .fn()
  .mockReturnValue('default')
  .mockReturnValueOnce('first call')
  .mockReturnValueOnce('second call');

// 'first call', 'second call', 'default', 'default'
console.log(myMockFn(), myMockFn(), myMockFn(), myMockFn());
  1. mockFn.mockResolvedValue(value)mock异步函数的语法糖

实现上类似于

jest.fn().mockImplementation(() => Promise.resolve(value));

用于在test中模拟异步函数

test('async test', async () => {
  const asyncMock = jest.fn().mockResolvedValue(43);

  await asyncMock(); // 43
});
  1. mockFn.mockResolvedValueOnce(value):语法糖

实现上类似于

jest.fn().mockImplementationOnce(() => Promise.resolve(value));
test('async test', async () => {
  const asyncMock = jest
    .fn()
    .mockResolvedValue('default')
    .mockResolvedValueOnce('first call')
    .mockResolvedValueOnce('second call');

  await asyncMock(); // first call
  await asyncMock(); // second call
  await asyncMock(); // default
  await asyncMock(); // default
});
  1. mockFn.mockRejectedValue(value):语法糖

实现上类似于

jest.fn().mockImplementation(() => Promise.reject(value));
test('async test', async () => {
  const asyncMock = jest.fn().mockRejectedValue(new Error('Async error'));

  await asyncMock(); // throws "Async error"
});
  1. mockFn.mockRejectedValueOnce(value):语法糖

实现上类似于

jest.fn().mockImplementationOnce(() => Promise.reject(value));
test('async test', async () => {
  const asyncMock = jest
    .fn()
    .mockResolvedValueOnce('first call')
    .mockRejectedValueOnce(new Error('Async error'));

  await asyncMock(); // first call
  await asyncMock(); // throws "Async error"
});
  1. mockFn.mockClear():重置所有存储在mockFn.mock.callsmockFn.mock.instances数组中的信息

当你想要清除两个断言之间的模拟使用数据时,这通常很有用。

  1. mockFn.mockReset():完成mockFn.mockClear()所做的所有事情,还删除任何模拟的返回值或实现

当你想要将模拟完全重置回其初始状态时,这非常有用。(请注意,重置spy将导致函数没有返回值)。

  1. mockFn.mockRestore():完成mockFn.mockReset()所做的所有事情,并恢复原始(非模拟)实现

当你想在某些测试用例中模拟函数并在其他测试用例中恢复原始实现时,这非常有用。

参考文档