jestjs / jest

Delightful JavaScript Testing.
https://jestjs.io
MIT License
44.24k stars 6.46k forks source link

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

Closed jacob-fueled closed 3 weeks ago

jacob-fueled commented 5 years ago

🐛 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 ```js // __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 commented 5 years ago

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

jacob-fueled commented 5 years ago

@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 commented 5 years ago

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

jacob-fueled commented 5 years ago

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 commented 5 years ago

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 commented 5 years ago

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 commented 5 years ago

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 commented 4 years ago

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 commented 4 years ago

@jacob-fueled have you fixed this issue?

jacob-fueled commented 4 years ago

@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 commented 4 years ago

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 commented 4 years ago

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 commented 3 years ago

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

thisismydesign commented 3 years ago

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 commented 3 years ago

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

Could we have something on this somewhere in the documentation ?

OndeVai commented 3 years ago

@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 commented 3 years ago

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 commented 3 years ago

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 commented 3 years ago

For me it was jest.resetAllMocks();!

aslansservant42 commented 3 years ago

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 commented 3 years ago

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

mwalkerr commented 3 years ago

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 commented 3 years ago

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 commented 2 years ago

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 commented 2 years ago

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 commented 1 year ago

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

inesverissimodnxt commented 1 year ago

@thisismydesign your solution helped me! Thanks

sw33tsoda commented 1 year ago

Solution:

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

github-actions[bot] commented 1 month ago

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[bot] commented 3 weeks ago

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.