jest使用指南--mock篇(代码片段)

Felix皇子 Felix皇子     2023-02-16     705

关键词:

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()。

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中的mock,jest.fn()jest.spyon()jest.mock()(代码片段)

...有必要。Mock函数提供的以下三种特性,在我们写测试代码时十分有用:捕获函数 查看详情

前端自动化测试框架jest中的mock(代码片段)

众所周知,在前面的代码中,我们在编写一个测试用例的时候用到了test、expect、toBe三个方法,在之前我们也介绍了Jest的基础使用、Jest的简单配置、Jest中的匹配器(matchers)、Jest命令行工具的使用和异步代码... 查看详情

jest使用指南--基础篇(代码片段)

Jest使用指南--基础篇#jestJest基础Jest语法descriptionDescribe,一种用于包含一个或多个相关测试的Jest方法。每次在开始为功能编写新的测试套件时,都将其包装在describe块中。description('一组测试的名称',()=> //测试的方法... 查看详情

jest使用指南--tsx组件篇(代码片段)

Jest使用指南--tsx组件篇#jest首先考虑一下,React组件我们需要测试哪些东西呢?React组件的render结果是一个组件树,并且整个树最终会被解析成一个纯粹由HTML元素构成的树形结构React组件可以拥有state,且state的变化... 查看详情

jest(代码片段)

...库,大部分其它工具将让你在测试中(甚至经常在你的主代码中)写一些不尽如人意的样板代码,以使其生效。Jest与Jasmine框架的区别是在后者之上增加了一些层。最值得注意的是,运行测试时,Jest会自动模拟依赖。Jest自动为... 查看详情

jest使用指南--ts文件篇(代码片段)

Jest使用指南--ts文件篇#jestts文件的单测ts文件主要是一些工具方法的使用,然后对于其中的工具方法进行单测,比对返回的数据结构,包括基础数据类型、对象、数组、函数异步方法这部分直接参考测试异步代码·Jest... 查看详情

jest使用指南--ts文件篇(代码片段)

Jest使用指南--ts文件篇#jestts文件的单测ts文件主要是一些工具方法的使用,然后对于其中的工具方法进行单测,比对返回的数据结构,包括基础数据类型、对象、数组、函数异步方法这部分直接参考测试异步代码·Jest... 查看详情

每次测试都是mock模块(代码片段)

我对在Jest中嘲笑如何对实现进行单元测试感到困惑。问题是我想模仿不同的预期行为。有没有办法实现这个目标?因为导入只能在文件的顶部,并且能够模拟在导入之前必须声明的内容。我也尝试传递一个本地函数,所以我可... 查看详情

jest-mock-extended - 使用对象输入调用模拟 [Typescript]

...题描述】:我在测试中使用jest-mock-extended。我想测试以下代码:classAconstructor(privateb:B)publictest(x:string): 查看详情

如何使用 jest.mock 模拟 useRef 和反应测试库

...回模拟当前值。我试过jest.mock但它返回一个HTMLDivElement。代码:constref=useRef<HTMLDivElement|nul 查看详情

vue开发实战实战篇#35:如何高效地使用mock数据进行开发(代码片段)

说明【Vue开发实战】学习笔记。效果我们通过mock数据来进行开发,比如下面的图表数据来自我们的mock数据调用接口http://localhost:8080/api/dashboard/chart?id=kaimo313安装依赖npminstall--saveaxios编写mock新建ant-design-vue-pro\\mock\\dashboard_c... 查看详情

jest使用指南--基础篇(代码片段)

Jest使用指南--基础篇#jestJest基础Jest语法descriptionDescribe,一种用于包含一个或多个相关测试的Jest方法。每次在开始为功能编写新的测试套件时,都将其包装在describe块中。description('一组测试的名称',()=> //测试的方法... 查看详情

使用 jest.mock('axios') 时如何模拟拦截器?

】使用jest.mock(\\\'axios\\\')时如何模拟拦截器?【英文标题】:Howtomockinterceptorswhenusingjest.mock(\'axios\')?使用jest.mock(\'axios\')时如何模拟拦截器?【发布时间】:2020-06-1003:18:56【问题描述】:使用jest运行测试时,我有基本的测试套装... 查看详情

jest使用指南--tsx组件篇(代码片段)

Jest使用指南--tsx组件篇#jest首先考虑一下,React组件我们需要测试哪些东西呢?React组件的render结果是一个组件树,并且整个树最终会被解析成一个纯粹由HTML元素构成的树形结构React组件可以拥有state,且state的变化... 查看详情

使用 jest 时 axios mock 无法处理默认标头

...测试时都访问服务器。这是它自己的action.js文件中的操作代码。login(commit,user)r 查看详情

spock单元测试框架实战指南七-动态mock(代码片段)

这篇讲解Spock自带的mock功能如何和powermock组合使用,发挥更强大的作用动态Mock静态方法(Spock Where +PowerMock)在上一篇的例子中使用powermock让静态方法返回一个指定的值,那能不能每次返回不同的值呢?我们先看下什么场景... 查看详情

使用 Jest 模拟的服务导致“不允许 jest.mock() 的模块工厂引用任何超出范围的变量”错误

】使用Jest模拟的服务导致“不允许jest.mock()的模块工厂引用任何超出范围的变量”错误【英文标题】:ServicemockedwithJestcauses"Themodulefactoryofjest.mock()isnotallowedtoreferenceanyout-of-scopevariables"error【发布时间】:2017-11-2219:11:13【问... 查看详情

jest.mock():如何使用工厂参数模拟 ES6 类默认导入

】jest.mock():如何使用工厂参数模拟ES6类默认导入【英文标题】:jest.mock():HowtomockES6classdefaultimportusingfactoryparameter【发布时间】:2018-05-0406:17:13【问题描述】:模拟ES6类导入我想在我的测试文件中模拟我的ES6类导入。如果被模拟... 查看详情