Johnny
Johnny

Reputation: 1895

Mock.mockImplementation fails with "not a function"

I have a service class

Service.js

class Service {
}
export default new Service();

And I am trying to provide a mock implementation for this. If I use something like this:

jest.mock('./Service', () => { ... my mock stuff });

It works fine, however I'm not able to access any variables declared outside of the mock, which is a bit limiting as I'd like to reconfigure what the mock returns, etc.

I tried this (inspired by this other StackOverflow article: Service mocked with Jest causes "The module factory of jest.mock() is not allowed to reference any out-of-scope variables" error)

import service from './Service';

jest.mock('./Service', () => jest.fn);

service.mockImplementation(() => {
    return { ... mock stuff }
);

Unfortunately when I am trying to run this, I get the below error:

TypeError: _Service2.default.mockImplementation is not a function

Upvotes: 64

Views: 137342

Answers (9)

deleteme
deleteme

Reputation: 377

The mock is equal to jest.fn. You need to call jest.fn to create a mocked function.

So this:

jest.mock('./Service', () => jest.fn);

Should be:

jest.mock('./Service', () => jest.fn());

Upvotes: 19

Hendrik
Hendrik

Reputation: 149

ran into similar issues and resolved it by using .mockImplementationOnce

jest.mock('./Service', () => jest.fn()
  .mockImplementationOnce(() => {
    return { ... mock stuff }
  })
  .mockImplementationOnce(() => {
    return { ... mock other stuff }
  })
);

now when you run another test it will return the second mock object.

Upvotes: 13

Akaisteph7
Akaisteph7

Reputation: 6496

My file had no default export but instead various other exports, one of which I needed to mock. This ended up working for me:

// services.test.js
const services = require('./services');

...

const innerFunction = jest.fn(() => console.log("calling innerFunction"));
services.functionToMock = jest.fn(() => ({innerFunction}))

I was then later able to test my innerFunction was properly called by doing

...
expect(innerFunction).toHaveBeenCalledTimes(1);

Upvotes: 0

Łukasz Matusik
Łukasz Matusik

Reputation: 7

Remove async from your test and DO NOT await expect!

WRONG

it.only('throws error on private visibility', async () => {
    await expect(getSteamData(mockSteamId)).rejects.toThrow(ErrorMessage.PRIVATE_VISIBILITY);
  });

RIGHT

it.only('throws error on private visibility', () => {
     expect(getSteamData(mockSteamId)).rejects.toThrow(ErrorMessage.PRIVATE_VISIBILITY);
  });

Upvotes: -2

Vadzim Zhukouski
Vadzim Zhukouski

Reputation: 11

In my case mockImplementation didn't work because I copy paste tests and forgot to remove "async" keyword in this place it('test description', **async** () => {...}

Upvotes: 0

Carl G
Carl G

Reputation: 18260

My mistake was that I was resetting the mock before each test. If you do that, be sure to reconfigure the mock implementation.

For example, change this:

  let value;
  let onPropertyChange: OnPropertyChangeCallback = jest.fn((changes: any) => {
      value = changes["testValue"];
    });

  const user = userEvent.setup();

  beforeEach(() => {
    jest.resetAllMocks();
  });

to this:

  let value;
  let onPropertyChange: OnPropertyChangeCallback;

  const user = userEvent.setup();

  beforeEach(() => {
    jest.resetAllMocks();

    onPropertyChange = jest.fn((changes: any) => {
      value = changes["testValue"];
    });
  });

Upvotes: 1

guillaume
guillaume

Reputation: 719

I had same problem as @Janos, the other answers didn't help either. You could do two things :

  1. If you need to mock only a function from Service, in your test file:

    import service from './Service';
    
    jest.mock('./Service', () => jest.fn());
    
    service.yourFunction = jest.fn(() => { /*your mock*/ })
    

     

  2. If you need to mock the entire Module:

    Say your service.js is in javascript/utils, create a javascript/utils/_mocks_ and inside it create a service.js file, you can then mock the entire class in this file, eg:

    const myObj = {foo: "bar"}
    
    const myFunction1 = jest.fn(() => { return Promise.resolve(myObj) })
    
    const  myFunction2 = ...
    
    module.exports = {
      myFunction1,
      myFunction2
    }
    

    then in your test file you just add:

    jest.mock('./javascript/utils/service')
    

    ...functions exported from the mockfile will be then hit through your test file execution.

Upvotes: 53

Somnium
Somnium

Reputation: 1155

I had similar problem, and the cause was that ".spec.js" file had an

import jest from "jest-mock";

After removing this line, it worked.

Upvotes: 0

maxletou
maxletou

Reputation: 1725

You need to store your mocked component in a variable with a name prefixed by "mock" and make sure you return an object with a default property as you import your Service from the default in your "main.js" file.

// Service.js
class Service {
}
export default new Service();

// main.test.js (main.js contains "import Service from './Service';")

const mockService = () => jest.fn();

jest.mock('./Service', () => {
    return {
        default: mockService
    }
});

Upvotes: 5

Related Questions