Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Jest mock/spy returns undefined even when set up #9131

Closed
jacob-fueled opened this issue Nov 4, 2019 · 31 comments
Closed

Jest mock/spy returns undefined even when set up #9131

jacob-fueled opened this issue Nov 4, 2019 · 31 comments

Comments

@jacob-fueled
Copy link

jacob-fueled commented Nov 4, 2019

🐛 Bug Report

Mocking spy return does nothing

I'm about to lose my mind here; it seems the entire mock system is b0rked. I copy-paste the examples from the docs, and they don't work.

To Reproduce

// __mocks__/someUtil.js
export default {
  m1: jest.fn(() => 42),
}
// someUtil.test.js
import someUtil from './someUtil';

jest.mock('./someUtil')

console.log(someUtil.m1) // expected spy stuff
console.log(someUtil.m1()) // undefined 🤯
Other variations that also do nothing
// __mocks__/someUtil.js

throw; // does throw

export default {
  m1: jest.fn(() => throw), // does NOT throw
}

// ---

export default {
  m1: jest.fn().mockImplementation(() => 42),
}

// ---

export default {
  m1: jest.fn().mockImplementation(() => throw), // does NOT throw
}

// ---

export default {
  m1: jest.fn().mockReturnValue(42),
}

// ---

export default () => ({
  m1: jest.fn().mockImplementation(() => 42),
})

// ---

export default () => ({
  m1: jest.fn().mockReturnValue(42),
})

Expected behavior

As documented: it should return 42

envinfo

jest version: 24.7.1

@joelbarbosa
Copy link

I think you need to check something in your config, because everything is Ok.
You can check here your exemple:
https://repl.it/repls/GrossDapperLinuxkernel

  • Need you import from import someUtil from './__mocks__/someUtil' ?

@jacob-fueled
Copy link
Author

jacob-fueled commented Nov 6, 2019

@joelbarbosa thanks for chiming it. The example you provided doesn't appear to actually be mocking anything: the actual implementation is itself just a mock:

// someUtil.js
module.exports = {m1: jest.fn(() => 42)}

Not sure if that matters, but I would guess it does.

Also not quite apples-to-apples: module.exports vs export default

As I understand it, you are not meant to import the mock: jest.mock handles that.

@joelbarbosa
Copy link

Yes it's mocking, you can check in the imagens:
image
image
image

@jacob-fueled
Copy link
Author

I saw that it is logging a mock. I think it's not the same because jest.mock('someUtil) is not actually doing anything: the real implementation is itself a spy.

My project's jest configuration is coming from react-scripts. The only thing I've done is a setupConfig where I globally add the enzyme adapter for react 16.

@joelbarbosa
Copy link

Yes, I think jest.mock('someUtil) is working, jest is isolating it you can check more example here:
https://medium.com/@rickhanlonii/understanding-jest-mocks-f0046c68e53c

@jacob-fueled
Copy link
Author

jacob-fueled commented Nov 7, 2019

In your repl example, if I remove the jest.mock(), the test behaves exactly the same with exactly the same output, so it's definitely not doing anything.

@joelbarbosa
Copy link

joelbarbosa commented Nov 7, 2019

Oh yeah, of course it is doing.
when you add jest.mock('./someUtil') you are reassign all the exported functions to the mock.

as you can see, I'll change the behavior of my function.

jest.mock('./someUtil', () => ({
  m1: jest.fn(() => 'mock me again')
}));

console.log(someUtil.m1); // [Function: mockConstructor].....
console.log(someUtil.m1()); // mock me again

@jacob-fueled
Copy link
Author

I created a representative example (https://repl.it/repls/GaseousAcrobaticMainframe), but it does not behave the same locally: Locally, calling the mocked getItem() in the actual implementation returns undefined, but calling it in the test returns the expected Promise { { id: '29ed6982-3959-450e-b3cf-bf82b2fa14b1' } }. When I log getItem (without calling it), it returns a mock object in both the implementation and the test.

I'm using the same version of Jest (24.9.0). The only difference I can think of is my local was generated from create-react-app, so the Jest configuration comes from that.

@sheppard30
Copy link

sheppard30 commented Jan 17, 2020

@jacob-fueled have you fixed this issue?

@jacob-fueled
Copy link
Author

@MateuszS Yes: I stopped using Jest. I could not figure out what the problem was; it quite possibly was coming from create-react-app, but I'd already sank far too many hours trying to get an out-of-the-box feature to work :(

@dstapleton92
Copy link

dstapleton92 commented Aug 4, 2020

A little late here, but I was just having this exact issue. I discovered that someone had added resetMocks: true to the jest.config.js file. This means that the implementations of mock functions are reset before each test. So in our case, the mock function was being included in the mocked module at test runtime, but that mock had been reset, so it returned undefined.

Regarding the original issue build environment, it looks like react-scripts does indeed add resetMocks: true into the jest config. (https://github.com/facebook/create-react-app/blob/master/packages/react-scripts/scripts/utils/createJestConfig.js#L69) But you can override it on the jest key of your package.json. (https://github.com/facebook/create-react-app/blob/master/packages/react-scripts/scripts/utils/createJestConfig.js#L74)

@HenryCharlesAnderson
Copy link

HenryCharlesAnderson commented Aug 26, 2020

resetMocks: true was the culprite for me too.

mock implementations can work with resetMocks: true if you setup the mocks in beforeEach, or directly inside the test/it callbacks.

If you set up the mocks at the top level of the module, in the describe callback (but outside of any it/test callback), or in beforeAll, they get overwritten by resetMocks AFAICT

@thirionlogan
Copy link

@dstapleton92 Thank you so much, I sank a ton of time into google trying to find this answer!

@thisismydesign
Copy link

In my case I wanted to mock a value that got evaluated upon loading the file:

export const store = createStore(...);

This was always returning undefined, no resetMocks setting.

Instead I had to do:

export const store = () => {
  return createStore(...);
}

@tevariou
Copy link

@dstapleton92 thank you! I've spent hours on this.

Could we have something on this somewhere in the documentation ?

@OndeVai
Copy link

OndeVai commented Mar 31, 2021

@dstapleton92 - thank you again! I've spent half a day on this and I'm glad I targetted for CRA as the culprit and not Jest!

@tizzyapunkt
Copy link

Spent one day until I landed here and figured out that CRA is causing mocks to be reset by default. That should definitely not be a default behaviour.

@rene-leanix
Copy link

This also caught me totally by surprise. We've been using restoreMocks in our jest.config.js to ensure all mocked spies are reverted to their original implementation between tests.

But I would have never have expected the restoration of mock functions to restore their original implementations to affect mock functions created on-the-fly with jest.fn() as well. Is there actually a use-case for resetting jest.fn(() => 42) to jest.fn()??

I know (now) it's documented:
image

And it's probably consistent as both jest.fn(() => 42) and jest.spyOn(obj, 'method').mockReturnValue(42) add mock implementations. But I'm sure it will keep tripping people up.

@TCMiranda
Copy link

For me it was jest.resetAllMocks();!

@aslansservant42
Copy link

I have been having the same issue. The examples do not work. I get errors of "cannot call .then of undefined". I too am using react-scripts.

putting "resetMocks": false into the "jest" portion of my package.json file fixed the problem immediately.

I've been pulling my hair out over this for 2 days! I agree that this "attribute" should be more widely published.

Perhaps, the jest.fn and jest.mock() documentation can mention that if the mocks get reset then the mocks set up in the top of the test file will no longer work. Lots of people create apps via react scripts and this has to be happening to lots more people.

@justrhysism
Copy link

Just lost a heap of hours on this also. I really feel like resetMocks: true shouldn't apply to global mock implementations.

@mwalkerr
Copy link

mwalkerr commented Sep 2, 2021

Had a similar issue where I couldn't figure out why the import I was trying to mock was ending up undefined. I was trying

const mockMyImport = jest.fn()

jest.mock('my-module', () => ({
  ...jest.requireActual('my-module'),
  myImport: mockMyImport,
}));

describe(`MyComponent test`, () => {
  it('works', async () => {
    mockMyImport.mockImplementation(() => {
      console.log("in mocked implementation")
      return 42
    })
    const { toJSON } = render(<MyComponent />)
    expect(toJSON()).toMatchSnapshot();
  });
});

but in my component, logging myImport would show that it was always undefined. This, however, works:

import { myImport } from 'my-module'

jest.mock('my-module', () => ({
  ...jest.requireActual('my-module'),
  myImport: jest.fn(),
}));

describe(`MyComponent test`, () => {
  it('works', async () => {
    (myImport as jest.Mock).mockImplementation(() => {
      console.log("in mocked implementation")
      return 42
    })
    const { toJSON } = render(<MyComponent />)
    expect(toJSON()).toMatchSnapshot();
  });
});

So in other words, you have to access the mocked import by actually importing it in the test rather than using the variable that you assigned the import to be mocked by....

@AlexWang-16
Copy link

AlexWang-16 commented Sep 4, 2021

resetMocks: true was the culprite for me too.

mock implementations can work with resetMocks: true if you setup the mocks in beforeEach, or directly inside the test/it callbacks.

If you set up the mocks at the top level of the module, in the describe callback (but outside of any it/test callback), or in beforeAll, they get overwritten by resetMocks AFAICT

@HenryCharlesAnderson thanks for providing this information. I tried putting jest.mock(...) inside the it callback function and it's still not working. I tried putting it under beforeEach() function and that didn't work either. The only way I could get it working is to set resetMocks to false in package.json. Any ideas why this is the case?

Here's some sample code I've written to test this for more context. In this example, jest.resetMocks in package.json is set to false and it works. The moment I set it to true, this fails. As mentioned above, moving jest.mock() block into the callback function ofit() and beforeEach() functions do not work.

// simplefn.ts
const simplefn = (message: string): string => message;
export default simplefn;
// simplefn.test.ts
import simplefn from '../simplefn';

jest.mock('../simplefn', () => ({
  __esModule: true,
  default: jest.fn((msg) => 'hello'),
}));

beforeEach(() => {
  jest.restoreAllMocks();  // Contrary to my expectation, this line does not cause jest.mock() to become reset
});

describe('test simplefn', () => {
  it('works with jest.fn', () => {
    const result = simplefn('test');
    expect(result).toBe('hello');
    expect(simplefn).toHaveBeenCalledTimes(1);
  });
});

@aslansservant42
Copy link

I had the same problem in my React project. Putting resetMocks: false into package.json did not fix it. I know that jest.mock is not broken but I can't get the simple examples to work either. The weird thing is that when I use VSS interactive debugger, it shows the dummyPromise implementation is my mocked implementation, but there is no evidence of mocked function support. _isMockFunction is undefined.

I have found that I can implement the supposed jest.mock behavior by using jest.spyon, mockImplementation, and importing from mocks directory.

Broken Code

import {dummyPromise} from '../services/DummyService';

jest.mock('../services/DummyService');

describe('grasping at straws why mocking service promises is not working', ()=>{
    beforeEach(()=>{
        //jest.mock('../services/DummyService.js'); // still does not work
    })

    test('can get a mock', ()=>{
        //jest.mock('../services/DummyService.js'); // still does not work
        expect(jest.isMockFunction(dummyPromise)).toBeTruthy();
    })
})

As you can see, putting the jest.mock() into beforeEach or in the test has no change in behavior.

Working Code

/* eslint-disable jest/no-mocks-import */
import * as DummyService from '../services/DummyService';
import * as DummyServiceMocks from '../services/__mocks__/DummyService';

const mockDummyPromise = jest.spyOn(DummyService, 'dummyPromise');
mockDummyPromise.mockImplementation(DummyServiceMocks.dummyPromise);

describe('grasping at straws why mocking service promises is not working', ()=>{

    test('can get a mock', ()=>{
        expect(jest.isMockFunction(mockDummyPromise)).toBeTruthy();
    })
    
    test('can call the mock', async ()=>{
        expect(jest.isMockFunction(mockDummyPromise)).toBeTruthy();
        mockDummyPromise().then(data=>{
            expect(data).not.toBeNull();
            expect(data).toEqual('dummy dummy dummy');
        })
    })
})

Yes, esLint complains about the import from __mocks__ and also thinks that the mockDummyPromise isn't really a promise, but this code works.

services/DummyService.js

export const dummyPromise = () =>{
    return new Promise((resolve)=>{
        resolve('real dummy real dummy');
    });
}

services/mocks/DummyService.js

export const dummyPromise = () =>{
    return new Promise( resolve => resolve('dummy dummy dummy'));
}

@archy-bold
Copy link

None of the above worked and in my case the problem was that I was adding a mock in a __mocks__ directory next to the file, but the import used a 'scoped module'. I had to add a folder for the scoped module under the root mocks folder ie __mocks__/@module/file.js.

The documentation mentions it.

@JamesVal
Copy link

My issue was the resetMocks problem as well. WOW... Was never going to find that one lol. THANK YOU @dstapleton92!!

@inesverissimodnxt
Copy link

@thisismydesign your solution helped me! Thanks

@sw33tsoda
Copy link

Solution:

Append --resetMocks=false to your test script and re-run.

Copy link

This issue is stale because it has been open for 1 year with no activity. Remove stale label or comment or this will be closed in 30 days.

@github-actions github-actions bot added the Stale label Sep 17, 2024
Copy link

This issue was closed because it has been stalled for 30 days with no activity. Please open a new issue if the issue is still relevant, linking to this one.

@github-actions github-actions bot closed this as not planned Won't fix, can't repro, duplicate, stale Oct 17, 2024
Copy link

This issue has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs.
Please note this issue tracker is not a help forum. We recommend using StackOverflow or our discord channel for questions.

@github-actions github-actions bot locked as resolved and limited conversation to collaborators Nov 17, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests