50

I'm writing a Jest mock, but I seem to have a problem when defining a mocked function outside of the mock itself.

I have a class:

myClass.js

class MyClass {
  constructor(name) {
    this.name = name;
  }

  methodOne(val) {
    return val + 1;
  }

  methodTwo() {
    return 2;
  }
}

export default MyClass;

And a file using it:

testSubject.js

import MyClass from './myClass';

const classInstance = new MyClass('Fido');

const testSubject = () => classInstance.methodOne(1) + classInstance.name;

export default testSubject;

And the test:

testSubject.test.js

import testSubject from './testSubject';

const mockFunction = jest.fn(() => 2)

jest.mock('./myClass', () => () => ({
    name: 'Name',
    methodOne: mockFunction,
    methodTwo: jest.fn(),
}))


describe('MyClass tests', () => {
    it('test one', () => {
        const result = testSubject()

        expect(result).toEqual('2Name')
    })
})

However, I get the following error:

TypeError: classInstance.methodOne is not a function

If I instead write:

...
methodOne: jest.fn(() => 2)

Then the test passes no problem.

Is there a way of defining this outside of the mock itself?

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
glenrothes
  • 1,543
  • 1
  • 14
  • 17

12 Answers12

36

In my case, I had to mock a Node.js module. I'm using React and Redux in ES6, with Jest and Enzyme for unit tests.

In the file I'm using, and writing a test for, I'm importing the node modules as default:

import nodeModulePackage from 'nodeModulePackage';

So I needed to mock it as a default since I kept getting the error (0, _blah.default) is not a function..

My solution was to do:

jest.mock('nodeModulePackage', () => jest.fn(() => {}));

In my case, I just needed to override the function and make it return an empty object.

If you need to call a function on that node module, you'll do the following:

jest.mock('nodeModulePackage', () => ({ doSomething: jest.fn(() => 'foo') }));
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
jenkizenki
  • 741
  • 6
  • 14
15

I figured this out. It is to do with hoisting, see: Jest mocking reference error

The reason it had worked in a previous test, where I had done it, was because the testSubject was itself a class. This meant that when the testSubject was instantiated, it was after the variable declaration in the test file, so the mock had access to use it.

So in the above case it was never going to work.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
glenrothes
  • 1,543
  • 1
  • 14
  • 17
6

For the other Jest newbies out there, if you mock multiple functions from the same package or module like this:

jest.mock(
  'pathToModule',
  () => ({
    functionA: jest.fn(() => {
      return 'contentA';
    }),
  })
);

jest.mock(
  'pathToModule',
  () => ({
    functionB: jest.fn(() => {
      return 'contentB';
    }),
  })
);

The second mock will override the first, and you'll end up with functionA is not a function.

Just combine them if they're both coming from the same module.

jest.mock(
  'samePathToModule',
  () => ({
    functionA: jest.fn(() => {
      return 'contentA';
    }),
    functionB: jest.fn(() => {
      return 'contentB';
    }),
  })
);
Panpaper
  • 451
  • 1
  • 6
  • 16
  • You're my hero! I'm very new to Jest and this was the exact issue I was facing. I was mocking a login hook and a social login hook directly after, but as a separate jest.mock and it kept saying 'auth.useSocialLogin is not a function'. Your example above solved it. Thank you SO much!! – user.io Jan 30 '22 at 01:03
5

Defining mockOne as an unassigned let and then initialising the variable inside the mocking function worked for me:

let mockFunction

jest.mock('./myClass', () => () => {
    mockFunction = jest.fn(() => 2)
    return {
        name: 'Name',
        methodOne: mockFunction,
        methodTwo: jest.fn(),
    }
}))
AxeEffect
  • 6,345
  • 4
  • 37
  • 33
  • This fixed my problem too! I guess it has something to do with the order of how the file runs? Like, Jest runs the file (to check for any mocking and setup it needs to do), then later the file executes "normally" during the test. ¯\_(ツ)_/¯ – Derek Morrison Aug 01 '21 at 16:13
  • Just to tack onto this answer, I got a similar error to OP after upgrading to Jest 27. My mock was structured similar to this answer, but I needed to add `__esModule: true` to my return object in order for it to work. Hope this helps someone! – tklives Feb 15 '22 at 18:55
3

In my case, it was the importing, as described at Getting `TypeError: jest.fn is not a function` (posting here because my problem showed in jest.mock, so I looked here first).

The basic problem was that someone had put in

const jest = require('jest');

But that is wrong (should be jest-mock instead of jest). This had worked in Node.js 10 with the line as originally put. It did not work in Node.js 14. I'm guessing that they used different versions of Jest, perhaps indirectly (other packages needed updated for 14).

const jest = require('jest-mock');

Also, in a test file, it will probably already be done for you, so you don't need to require anything. The file will run properly without that line.

Linting may give an error with that line omitted, but that can be fixed with

/* global jest */

or if you're already doing that with expect,

/* global expect, jest */

The linter seemed happy when that line appeared either after or before jest was first used. You may choose order based on readability rather than by functional requirement if your linter works the same way.

mdfst13
  • 850
  • 8
  • 18
  • Where is the file you speak of? – crazyProgrammer Nov 30 '22 at 20:16
  • 1
    @crazyProgrammer Which file? The only file that I mention is a test file, which would be in the location where you put it. I.e. that would be file that you create. I'm saying that if you have properly set up things to run `npm test`, then in Node 14 (and presumably later), it will work without manually importing anything. It's just that the linter won't know about the environment calling your code, so you need to tell it that jest will be auto-created. That's what the global line does. If you're talking about jest-mock/jest, those would be in the jest package, under node_modules. – mdfst13 Dec 02 '22 at 11:34
2

In my case it was from the module.exports.

Instead of writing

module.exports = {sum: sum};

Write

module.exports = sum;

Note: sum is a function that adds 2 numbers

Gabriel Arghire
  • 1,992
  • 1
  • 21
  • 34
2

In case anyone is still facing a similar issue, to resolve the failing tests I had to return a mocked function like so:

const tea = TeamMaker.makeTea();
tea(); // TypeError: tea is not a function


jest.mock('url/to/TeamMaker', () => ({ makeTea: jest.fn(() => jest.fn) })); // tests passed
2

For me, it was the jest config.

Because my project was initially in .js and was upgrade to .ts.

So the class I tested was in .ts but my jest.config.js was config for .jsfile.

See below my new config :

 module.exports = {
  ...
  collectCoverageFrom: ['src/**/*.{ts,js,jsx,mjs}'],
  ...
  testMatch: [
    '<rootDir>/src/**/__tests__/**/*.{ts,js,jsx,mjs}',
    '<rootDir>/src/**/?(*.)(spec|test).{ts,js,jsx,mjs}',
  ]
};
1

I am writing my version in case it helps someone.

I was facing error while mocking firebase-admin. I jest-mocked firebase-admin like below:

jest.mock('firebase-admin');

But I started getting following error:

firebase_admin_1.default.messaging is not a function

It was coming because in the app code, I had used firebase-admin like this:

await admin.messaging().send(message) 

(message is an instance of TokenMessage)

The problem was that I was not mocking the member variables and member methods. Here it was the member method messaging and a nested member method within messaging called send. (See that messaging method is called on admin and then send is called on the value of admin.messaging()). All that was needed was to mock these like below:

jest.mock('firebase-admin', () => ({
  credential: {
    cert: jest.fn(),
  },
  initializeApp: jest.fn(),
  messaging: jest.fn().mockImplementation(() => {
    return {
      send: jest
        .fn()
        .mockReturnValue('projects/name_of_project/messages/message_id'),
    };
  }),
}));

(Note that I have also mocked other member variables/methods as per my original requirement. You would probably need these mocks as well)

Ankit Shubham
  • 2,989
  • 2
  • 36
  • 61
0

In my case, it was the way i was exporting the files that was the issue.

The file i was trying to mock was being exported like: export const fn = () => {};

The mocked file i wrote was being exported like:

const fn = () => {};
export default fn;

Once i made sure that the mocked file was also being exported like the file to be mocked, i didn't have this issue

0

Following Code worked for me

 import {testSubject} from '../testsubject';

 describe('sometest',(){

 it('some test scenario',()=>{
  
  testSubject.mockReturnValue(()=>jest.fn);

  })

 })
0

IT HAS TO BE EXPORTED. like node function.

e.g. helper.js has myLog function at the end,

module.exports = {
  sleep,
  myLog
};

now spec file can import and call them.

No tsx or jest.config.js changes...

CodeFarmer
  • 2,644
  • 1
  • 23
  • 32