使用 Jest 模拟 Es6 类

Posted

技术标签:

【中文标题】使用 Jest 模拟 Es6 类【英文标题】:Mock Es6 classes using Jest 【发布时间】:2018-05-04 18:56:37 【问题描述】:

我正在尝试使用接收参数的构造函数来模拟 ES6 类,然后使用 Jest 在该类上模拟不同的类函数以继续测试。

问题是我找不到任何有关如何解决此问题的文档。我已经看过this post,但它并没有解决我的问题,因为 OP 实际上甚至不需要模拟课程!该帖子中的另一个答案也根本没有详细说明,没有指向任何在线文档,也不会导致可复制的知识,因为它只是一段代码。

假设我有以下课程:

//socket.js;

module.exports = class Socket extends EventEmitter 

    constructor(id, password) 
        super();

        this.id = id;
        this.password = password;

        this.state = constants.socket.INITIALIZING;
    

    connect() 
        // Well this connects and so on...
     

;

//__tests__/socket.js

jest.mock('./../socket');
const Socket = require('./../socket');
const socket = new Socket(1, 'password');

expect(Socket).toHaveBeenCalledTimes(1);

socket.connect()
expect(Socket.mock.calls[0][1]).toBe(1);
expect(Socket.mock.calls[0][2]).toBe('password');

很明显,我试图模拟 Socket 和类函数 connect 的方式是错误的,但我找不到正确的方法所以。

请在你的回答中解释你为模拟这个而采取的逻辑步骤以及为什么每个步骤都是必要的 + 如果可能的话,请提供指向 Jest 官方文档的外部链接!

感谢您的帮助!

【问题讨论】:

我希望我能不止一次地对此表示赞同......我也在努力理解 ES6 类的模拟:***.com/questions/47402005/… 是的,文档确实缺乏解释这一点,因为我猜 Jest 更倾向于 ES6 之前的有点 javascript 【参考方案1】:

更新:

所有这些信息和更多信息现已添加到新指南“ES6 Class Mocks”中的 Jest 文档中。

完全披露:我写的。 :-)


模拟 ES6 类的关键是知道ES6 类是一个函数。因此,mock 也必须是一个函数

    调用jest.mock('./mocked-class.js');,同时导入'./mocked-class.js'。 对于要跟踪调用的任何类方法,创建一个指向模拟函数的变量,如下所示:const mockedMethod = jest.fn();。在下一步中使用它们。 致电MockedClass.mockImplementation()。传入一个箭头函数,该函数返回一个包含任何模拟方法的对象,每个方法都设置为自己的模拟函数(在第 2 步中创建)。 可以使用手动模拟(__mocks__ 文件夹)模拟 ES6 类来完成相同的操作。在这种情况下,导出的模拟是通过调用jest.fn().mockImplementation() 创建的,具有与上面(3)中描述的相同的参数。这将创建一个模拟函数。在这种情况下,您还需要导出任何要监视的模拟方法。 同样的事情可以通过调用jest.mock('mocked-class.js', factoryFunction) 来完成,其中 factoryFunction 与上面 3 和 4 中传递的参数相同。

一个例子值一千字,所以这里是代码。 此外,这里有一个 repo 演示了所有这些: https://github.com/jonathan-stone/jest-es6-classes-demo/tree/mocks-working

首先,对于您的代码

如果您要添加以下设置代码,您的测试应该会通过:

const connectMock = jest.fn(); // Lets you check if `connect()` was called, if you want

Socket.mockImplementation(() => 
    return 
      connect: connectMock
    ;
  );

(注意,在您的代码中:Socket.mock.calls[0][1] 应该是 [0][0][0][2] 应该是 [0][1]。)

接下来,一个人为的例子

内嵌一些解释。

mocked-class.js。请注意,此代码在测试期间永远不会被调用。

export default class MockedClass 
  constructor() 
    console.log('Constructed');
  

  mockedMethod() 
    console.log('Called mockedMethod');
  

mocked-class-consumer.js。此类使用模拟类创建一个对象。我们希望它创建一个模拟版本而不是真实版本。

import MockedClass from './mocked-class';

export default class MockedClassConsumer 
  constructor() 
    this.mockedClassInstance = new MockedClass('yo');
    this.mockedClassInstance.mockedMethod('bro');
  

mocked-class-consumer.test.js - 测试:

import MockedClassConsumer from './mocked-class-consumer';
import MockedClass from './mocked-class';

jest.mock('./mocked-class'); // Mocks the function that creates the class; replaces it with a function that returns undefined.

// console.log(MockedClass()); // logs 'undefined'

let mockedClassConsumer;
const mockedMethodImpl = jest.fn();

beforeAll(() => 
  MockedClass.mockImplementation(() => 
    // Replace the class-creation method with this mock version.
    return 
      mockedMethod: mockedMethodImpl // Populate the method with a reference to a mock created with jest.fn().
    ;
  );
);

beforeEach(() => 
  MockedClass.mockClear();
  mockedMethodImpl.mockClear();
);

it('The MockedClassConsumer instance can be created', () => 
  const mockedClassConsumer = new MockedClassConsumer();
  // console.log(MockedClass()); // logs a jest-created object with a mockedMethod: property, because the mockImplementation has been set now.
  expect(mockedClassConsumer).toBeTruthy();
);

it('We can check if the consumer called the class constructor', () => 
  expect(MockedClass).not.toHaveBeenCalled(); // Ensure our mockClear() is clearing out previous calls to the constructor
  const mockedClassConsumer = new MockedClassConsumer();
  expect(MockedClass).toHaveBeenCalled(); // Constructor has been called
  expect(MockedClass.mock.calls[0][0]).toEqual('yo'); // ... with the string 'yo'
);

it('We can check if the consumer called a method on the class instance', () => 
  const mockedClassConsumer = new MockedClassConsumer();
  expect(mockedMethodImpl).toHaveBeenCalledWith('bro'); 
// Checking for method call using the stored reference to the mock function
// It would be nice if there were a way to do this directly from MockedClass.mock
);

【讨论】:

感谢您的回答,好吧,它实际上解决了我的问题,对事情的运作方式有了一点了解。但是有一个问题,我应该将 Socket.mockImplementation 放在我编写的每个测试中(test("it ...")),否则,全局模拟方法上的计数器只会增加(第一个测试将检查 .calls.length 是否为 1,下一个应检查它是否为 2,依此类推......)。你会怎么解决?我尝试在 beforeEach 函数中运行 Socket.mockImplementation,但没有运气! beforeEach() 中致电Socket.mock.mockClear()。 facebook.github.io/jest/docs/en/… 是的,现在看看,虽然 Socket.mockClear() 是有效的。我接受您的答案作为正确答案,直到出现更好的答案。非常感谢! Demo repo 展示了以三种不同方式模拟 ES6 类:在 _mocks_ 文件夹中使用手动模拟;工厂功能;和手动模拟加mockImplementation():github.com/jonathan-stone/jest-es6-classes-demo/tree/… 我自己发现的。有时人们不得不提出一个问题,只是为了找到寻找什么的提示。 :) 我不得不告诉 typescript 我的课现在是 Mock 了。所以在'jest.mock('./MyClass');'之后我做'const myClass = MyClass as unknown as jest.Mock;' TS 编译器很高兴。【参考方案2】:

对我来说,这种用模拟类替换真实类的方法很有效。

// Content of real.test.ts

jest.mock("../RealClass", () => 
  const mockedModule = jest.requireActual(
    "../test/__mocks__/RealClass"
  );
  return 
    ...mockedModule,
  ;
);

var codeTest = require("../real");
  it("test-real", async () => 
    let result = await codeTest.handler();
    expect(result).toMatch(/mocked.thing/);
  );

// Content of real.ts
import RealClass from "../RealClass";
export const handler = 
   let rc = new RealClass(doing:'something');
   return rc.realMethod("myWord");

// Content of ../RealClass.ts
export class RealClass 
  constructor(something: string) 
  async realMethod(input:string) 
    return "The.real.deal "+input;
  
// Content of ../test/__mocks__/RealClass.ts
export class RealClass 
  constructor(something: string) 
  async realMethod(input:string) 
    return "mocked.thing "+input;
  

对不起,如果我拼错了什么,但我正在写。

【讨论】:

这里不需要使用 ModuleFactory 语法。 Jest 会自动将 RealClass 替换为 __mocks__/RealClass。见:jestjs.io/docs/en/es6-class-mocks.html#automatic-mock

以上是关于使用 Jest 模拟 Es6 类的主要内容,如果未能解决你的问题,请参考以下文章

如何模拟 ES6 超类并用 jest.js 监视它?

Jest Manual Mocks with React 和 Typescript:模拟 ES6 类依赖

使用 Typescript 从 Jest 手动模拟中导入函数

ES6类开玩笑嘲弄

Jest React 测试 es6 导入/导出不需要的模拟

如何使用Jest在ES6类方法中测试对redux存储的调度?