The reason why a mock function returns undefined in Jest mock

Ats
3 min readJan 20, 2024

--

It took a few hours to understand how the jest.mock works. I put the note about my learnings here.

Photo by David Tyemnyák on Unsplash

Background

Normally, I write ReactNative code for my job and refactored the test codes last week. Then I tried to make a function to mock a React component in order to put the same process in a test utility file. It was like below.

export function mockComponent<T>(componentName: string, mockFuntion: (props: T) => void) {
const React = require('react');
const Component = class extends React.PureComponent {
render() {
const { children } = this.props;
mockFuntion(this.props);

return React.createElement('View', this.props, children);
}
};

Component.displayName = 'View';

return { [componentName]: Component };
}

This is for testing if the mock component is passed to with specific props through mockFunction . Then I tried to import the utility and run like below.

import { render } from '@testing-library/react-native';
import { ParentComponent } from 'path/to/ParentComponent';
import { mockComponent } from 'path/to/mockComponent';

const mockChildFunction = jest.fn();
jest.mock('path/to/ChildComponent', () => {
return mockComponent('ChildComponent', mockChildFunction);
});

describe('ParentComponent', () => {
test('checks if mockChildFunction is called', () => {
const specificProps = { ... }
render(<ParentComponent />);
expect(mockChildFunction).toHaveBeenCalled({...specificProps});
});
});

But it didn’t work well. Then I started to investigate why.

What I did

I found the description in the official documents of Jest.

Since calls to jest.mock() are hoisted to the top of the file, Jest prevents access to out-of-scope variables. By default, you cannot first define a variable and then use it in the factory. Jest will disable this check for variables that start with the word mock. However, it is still up to you to guarantee that they will be initialized on time. Be aware of Temporal Dead Zone.

Basically, The paragraph explains all my mistakes. There were three things which I didn’t know.

  1. Since calls to jest.mock() are hoisted to the top of the file, Jest prevents access to out-of-scope variables.
  2. Jest will disable this check for variables that start with the word mock
  3. it is still up to you to guarantee that they will be initialized on time.

Firstly, you can’t access variables defined in your test file from the jest.mock block. I didn’t know that and tried to call mockComponent in the block. Then the result returns mockComponent is undefined . So I put the import in the block.

Secondly, only variables which start with mock are accessible from the jest.mock block. I named my mock functions with mock prefix unintentionally because there are a lot of documents and articles using the prefix. I didn’t know there was the reason behind the naming.

Lastly, you have to care about the timing when mock functions starting with mock are called by yourself. Technically, when Jest processes jest.mock blocks, the mock function is undefined yet. So if you assign or execute the mock function, it won’t work as you expect because it’s undefined. To avoid that, you need to use an anonymous function because the mock function isn’t undefined when the anonymous function is called.

The following code snippet is my final output.

import { render } from '@testing-library/react-native';
import { ParentComponent } from 'path/to/ParentComponent';

const mockChildFunction = jest.fn();
jest.mock('path/to/ChildComponent', () => {
const { mockComponent } = require('../../../test/utilities');
const callback = prop => mockChildFunction(props);
return mockComponent('ChildComponent', callback);
});

describe('ParentComponent', () => {
test('checks if mockChildFunction is called', () => {
const specificProps = { ... }
render(<ParentComponent />);
expect(mockChildFunction).toHaveBeenCalled({...specificProps});
});
});

That’s it!

--

--

Ats
Ats

Written by Ats

I like building something tangible like touch, gesture, and voice. Ruby on Rails / React Native / Yocto / Raspberry Pi / Interaction Design / CIID IDP alumni

No responses yet