ES6 class jest mocking

2019-09-02 07:13发布

问题:

I have an ES6 class which I need to mock it's methods. Following the documentation i made a manual mock of this, and got the constructor to both be called and asserted.

My function that consumes this class is just a basic function that runs one of the class methods.

test.js

const mockConnect = jest.fn();
const mockAccess = jest.fn();
jest.mock('../../src/connection');
const connection = require('../../src/connection').default;

connection.mockImplementation(() => {
  return {
    connect: mockConnect,
    access: mockAccess.mockReturnValue(true),
  };
});

caller_function(); 
expect(connection).toHaveBeenCalled(); // works properly as the constructor is called
expect(connection).toHaveBeenCalledWith('something'); // works
expect(mockAccess).toHaveBeenCalled(); // says it was not called when it should have

caller_function.js

import connection from 'connection';
const conn = new connection('something');

export function caller_function() {
  conn.access(); // returns undefined when mock says it should return true
}

回答1:

This is happening because you're using mockImplementation() instead of a manual mock or the factory parameter to jest.mock(), and your mocked object is being created during the module loading process, since the constructor call is not inside of any function. What's happening is:

  1. The call to jest.mock('../../src/connection') runs and sets connection to be an automatic mock.
  2. The conn object is created using the automatic mock. Therefore its access method returns undefined.
  3. The call to mockImplementation() happens, changing the connection mock. However, since the conn object has already been created, it doesn't get the custom implementation.

Moving the constructor call into caller_function is one way to fix it:

export function caller_function() {
  const conn = new connection('something');
  conn.access();
}

You could also use the factory parameter to jest.mock(), specifying the implementation there, instead of calling mockImplementation(). That way you won't have to change your implementation code:

const mockConnect = jest.fn();
const mockAccess = jest.fn();
import connection from '../../src/connection';
jest.mock('./so-import', () => {
  return jest.fn().mockImplementation(() => {
    return {
      connect: mockConnect,
      access: mockAccess.mockReturnValue(true)
    };
  });
});

...

BTW the convention for ES6 class names is to begin with an uppercase letter. I was temporarily confused by the lowercase name connection.



回答2:

Did you try doing connection.mockClear(); before you write a mockImplementation for the methods? Also please refer to this https://jestjs.io/docs/en/es6-class-mocks