ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

Jest 使用指南 - - Mock 篇

2021-06-27 16:33:03  阅读:282  来源: 互联网

标签:Jest 函数 callback mock jest fn 使用指南 Mock expect


Jest 使用指南 - - Mock 篇

#jest

Jest Mock

为什么会用到 Mock? Mock 能帮我们解决什么问题?
在项目中,一个模块的方法内常常会去调用另外一个模块的方法。在单元测试中,我们可能并不需要关心内部调用的方法的执行过程和结果,只想知道它是否被正确调用即可,甚至会指定该函数的返回值。此时,使用Mock函数是十分有必要。
Mock函数提供的以下三种特性,在我们写测试代码时十分有用:
- 擦除函数的实际实现(换句话说:改变函数的内部实现)
- 捕获函数调用情况( 包括:这些调用中传递的参数、new 的实例)
- 设置函数返回值

jest.fn()

Jest.fn()是创建Mock函数最简单的方式,如果没有定义函数内部的实现,jest.fn()会返回undefined作为返回值。
Jest.fn()所创建的Mock函数还可以设置返回值,定义内部实现或返回Promise对象。

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

test("测试jest.fn()内部实现", () => {
    let mockFn = jest.fn((x) => 42 + x);
    // 断言mockFn执行后返回 52
    expect(mockFn(10)).toBe(52);
});

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

jest.mock()

ES6 Class Mocks · Jest
有下面几种方式来 mock 模块

  1. 使用 jest.mock 自动 mock
    jest.mock('./utils.ts') 自动返回一个 mock ,可以使用它来监视对类构造函数及其所有方法的调用。方法调用保存在中
    theAutomaticMock.mock.instances[index].methodName.mock.calls
    ⚠️:如果您在类中使用箭头函数,则它们不会成为模拟的一部分。这样做的原因是,箭头函数不存在于对象的原型中,它们只是持有对该函数的引用的属性。
  2. jest.mock()直接在单元测试里面mock 模块
    jest.mock(path, moduleFactory) 接受模块工厂参数。模块工厂是一个返回模拟的函数。为了模拟构造函数,模块工厂必须返回构造函数。换句话说,模块工厂必须是返回函数的函数-高阶函数(HOF)。
    例如在 node 端会通过 fs 来读取文件,在单元测试中, 我们并不需要真去调用fs读取文件, 就可以考虑把fs模块mock掉, 如下代码:
jest.mock('fs', () => ({
    readFileSync: jest.fn()
}))

⚠️:这么使用有个限制就是,因为调用 jest.mock() 被提升到了文件开头,因此不可以先定义一个变量,然后在工厂中使用它。以 "mock "开头的变量是一个例外。这取决于你是否能保证它们按时被初始化!
3. 在需要mock的模块目录临近建立目录__mocks__
对于用户目录下面的模块
例如我们需要mock目录models下面的user模块,那么我们就需要在models下面新建__mocks__目录(这里要区分大小写),然后新建文件user.js。
注意:用这种方式, 需要在单元测试文件中需添加下面的代码才能使此mock生效。jest.mock('./moduleName')

// mock.ts
import utils from "./utils";
export default {
    test() {
        return utils.add(1, 2);
    },
};

// utils.ts
export default {
    add(a: number, b: number): number {
        console.log("----", a, b);
        return a + b;
    },
};

// mock.test.ts
import m from "./mock";
import utils from "./utils";
jest.mock("./utils.ts");
test("mock 整个 fetch.js模块", () => {
    m.test();
    expect(y).toBeCalledTimes(1);
});

终端结果
在这里插入图片描述

jest.spyOn()

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

import m from "./mock";
import utils from "./utils";
test("mock 整个 fetch.js模块", () => {
    const y = jest.spyOn(utils, "add");
    m.test();
    expect(y).toBeCalledTimes(1);
});

上面 jest.mock() 终端截图中可以看到,console 并没有打印,这是因为通过jest.mock()后,模块内的方法是不会被jest所实际执行的。这时我们就需要使用jest.spyOn()。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-g7zAkxwC-1624781830084)(Jest%20%E4%BD%BF%E7%94%A8%E6%8C%87%E5%8D%97%20-%20-%20Mock%20%E7%AF%87/465D3153-F63E-4BF9-A017-D2A3F6690FF6.png)]

Time mock

我们时常会用到 setTimeout 、setInterval、 clearTimeout 、clearInterval,针对这些定时器我们怎么去控制单测呢。
根据官方文旦time mock的说明,这里可以分为三种 Run All time(快进所有的定时时间)、Advance Timer by Time(快进指定的时间)、Run Pending Timer(针对嵌套了定时器的场景)

function timerGame(callback) {
  console.log('Ready....go!');
  setTimeout(() => {
    console.log("Time's up -- stop!");
    callback && callback();
  }, 1000);
}
module.exports = timerGame;

Run All Timer

jest.useFakeTimers();
test('calls the callback after 1 second', () => {
  const timerGame = require('../timerGame');
  const callback = jest.fn();
  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.runAllTimers();
  // Now our callback should have been called!
  expect(callback).toBeCalled();
  expect(callback).toHaveBeenCalledTimes(1);
});

注意:这里我们通过调用 jest.useFakeTimers(); 来启用假定时器。这用模拟函数模拟了 setTimeout 和其他计时器函数。

Advance TImer by Times

原先的方法名和上面的 runAllTimers 是呼应的,叫做 runTimersToTime,在 v22.0.0 后重命名为 advanceTimersByTime ,这个函数的作用就是快进指定的时间,

it('calls the callback after 1 second via advanceTimersByTime', () => {
  const timerGame = require('../timerGame');
  const callback = jest.fn();

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

就上面这个而言,其实和 runAllTimer 是一样的,因为我们指定的时间就是全部的时间,下面我们可以尝试一下设置另外的时间


it("calls the callback after 999 millsecond via advanceTimersByTime", () => {
    const callback = jest.fn();
    timerGame(callback);
    expect(callback).not.toBeCalled();
	// 改为 999 毫秒,不到 1秒
    jest.advanceTimersByTime(999);
    expect(callback).not.toBeCalled();
    expect(callback).toHaveBeenCalledTimes(0);
	 // 又快进了 1 毫秒
    jest.advanceTimersByTime(1);
    expect(callback).toBeCalled();
    expect(callback).toHaveBeenCalledTimes(1);
});

上面的例子我们将指定时间改为。999 毫秒,不到 1 秒,所以相应的回调函数还没执行,根据运行结果的确是的,之后我们又只快进了 1毫秒,对应的 callback 执行了,符合我们的预期

最后一种 Run Pending Timer 就不多讲了,遇到了再来看,相对来说上面两种使用的场景较多

总结

在实际项目的单元测试中,jest.fn()常被用来进行某些有回调函数的测试;jest.mock()可以mock整个模块中的方法,当某个模块已经被单元测试100%覆盖时,使用jest.mock()去mock该模块,节约测试时间和测试的冗余度是十分必要;当需要测试某些必须被完整执行的方法时,常常需要使用jest.spyOn()。这些都需要开发者根据实际的业务代码灵活选择。

标签:Jest,函数,callback,mock,jest,fn,使用指南,Mock,expect
来源: https://blog.csdn.net/F_Felix/article/details/118275875

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有