score:60

Accepted answer

The problem

The reason you're getting that error has to do with how various operations are hoisted.

Even though in your original code you only import SearchContainer after assigning a value to mockSearch and calling jest's mock, the specs point out that: Before instantiating a module, all of the modules it requested must be available.

Therefore, at the time SearchContainer is imported, and in turn imports search , your mockSearch variable is still undefined.

One might find this strange, as it would also seem to imply search.js isn't mocked yet, and so mocking wouldn't work at all. Fortunately, (babel-)jest makes sure to hoist calls to mock and similar functions even higher than the imports, so that mocking will work.

Nevertheless, the assignment of mockSearch, which is referenced by the mock's function, will not be hoisted with the mock call. So, the order of relevant operations will be something like:

  1. Set a mock factory for ./search.js
  2. Import all dependencies, which will call the mock factory for a function to give the component
  3. Assign a value to mockSearch

When step 2 happens, the search function passed to the component will be undefined, and the assignment at step 3 is too late to change that.

Solution

If you create the mock function as part of the mock call (such that it'll be hoisted too), it'll have a valid value when it's imported by the component module, as your early example shows.

As you pointed out, the problem begins when you want to make the mocked function available in your tests. There is one obvious solution to this: separately import the module you've already mocked.

Since you now know jest mocking actually happens before imports, a trivial approach would be:

import { search } from './search.js'; // This will actually be the mock

jest.mock('./search.js', () => {
  return { search: jest.fn(() => mockPromise) };
});

[...]

beforeEach(() => {
  search.mockClear();
});

it('should call the search module', () => {
  [...]

  expect(search.mock.calls.length).toBe(1);
  expect(search.mock.calls[0]).toEqual(expectedArgs);
});

In fact, you might want to replace:

import { search } from './search.js';

With:

const { search } = require.requireMock('./search.js');

This shouldn't make any functional difference, but might make what you're doing a bit more explicit (and should help anyone using a type-checking system such as Flow, so it doesn't think you're trying to call mock functions on the original search).

Additional note

All of this is only strictly necessary if what you need to mock is the default export of a module itself. Otherwise (as @publicJorn points out), you can simply re-assign the specific relevant member in the tests, like so:

import * as search from './search.js';

beforeEach(() => {
  search.search = jest.fn(() => mockPromise);
});

score:0

When mocking an api call with a response remember to async() => your test and await the wrapper update. My page did the typical componentDidMount => make API call => positive response set some state...however the state in the wrapper did not get updated...async and await fix that...this example is for brevity...

...otherImports etc...
const SomeApi = require.requireMock('../../api/someApi.js');

jest.mock('../../api/someApi.js', () => {
    return {
        GetSomeData: jest.fn()
    };
});

beforeEach(() => {
    // Clear any calls to the mocks.
    SomeApi.GetSomeData.mockClear();
});

it("renders valid token", async () => {
        const responseValue = {data:{ 
            tokenIsValid:true}};
        SomeApi.GetSomeData.mockResolvedValue(responseValue);
        let wrapper = shallow(<MyPage {...props} />);
        expect(wrapper).not.toBeNull();
        await wrapper.update();
        const state = wrapper.instance().state;
        expect(state.tokenIsValid).toBe(true);

    });

score:2

In my case, I got this error because I failed to implement the mock correctly.

My failing code:

jest.mock('react-native-some-module', mockedModule);

When it should have been an arrow function...

jest.mock('react-native-some-module', () => mockedModule);


Related Query

More Query from same tag