使用Jest模拟Es6类

时间:2017-11-21 10:35:31

标签: javascript node.js unit-testing ecmascript-6 jestjs

我试图使用接收参数的构造函数来模拟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');

很明显,我试图模仿套接字和类功能连接的方式是错误的,但是我无法找到正确的方法。

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

感谢您的帮助!

2 个答案:

答案 0 :(得分:17)

更新

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

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

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

  1. 致电jest.mock('./mocked-class.js');,并导入'./mocked-class.js'。
  2. 对于要跟踪调用的任何类方法,创建一个指向模拟函数的变量,如下所示:const mockedMethod = jest.fn();。请在下一步中使用它们。
  3. 致电MockedClass.mockImplementation()。传入一个箭头函数,该函数返回一个包含任何模拟方法的对象,每个方法都设置为自己的模拟函数(在步骤2中创建)。
  4. 使用手动模拟(__mocks__文件夹)来模拟ES6类可以做同样的事情。在这种情况下,导出的模拟是通过调用jest.fn().mockImplementation()创建的,具有与上面(3)中描述的相同的参数。这会创建一个模拟函数。在这种情况下,您还需要导出任何想要监视的模拟方法。
  5. 同样的事情可以通过调用jest.mock('mocked-class.js', factoryFunction)来完成,其中factoryFunction也是上面3和4中传递的相同参数。
  6. 一个例子胜过千言万语,所以这就是代码。 此外,还有一个回购展示了所有这些,在这里: 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]。)

    接下来,一个人为的例子

    内联一些解释。

    <强>嘲笑-class.js 即可。注意,在测试过程中从不调用此代码。

    export default class MockedClass {
      constructor() {
        console.log('Constructed');
      }
    
      mockedMethod() {
        console.log('Called mockedMethod');
      }
    }
    

    <强>嘲笑级-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
    });
    

答案 1 :(得分:0)

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

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

对不起,如果我拼错了一些东西,但我正在写。