I'm trying to mock only one function in imported React module, keep the rest of the module unmocked and do this at top level for all tests.
I'm using fresh create-react-app project with a single test to observe the problem.
Steps to reproduce:
create-react-app test
src/App.test.js
as the only test filenpm run test
App.test.js
jest.mock('react', () => {
jest.dontMock('react');
const React = require('react');
const lazy = jest.fn();
return {
...React,
lazy
};
});
import * as React from 'react';
const React2 = require('react');
it('should partially mock React module', async () => {
expect(jest.isMockFunction(React.lazy)).toBe(true); // passes
expect(jest.isMockFunction(React2.lazy)).toBe(true); // fails
expect(jest.isMockFunction(require('react').lazy)).toBe(true); // fails
expect(jest.isMockFunction((await import('react')).lazy)).toBe(true); // fails
});
The problem here seems to be jest.dontMock
as it prevents require
and dynamic import
from being mocked, but it remains unclear why it was possible to mock static import
this way, as it uses require
any way. Here's transpiled file:
"use strict";
jest.mock('react', () => {
jest.dontMock('react');
const React = require('react');
const lazy = jest.fn();
return (0, _objectSpread2.default)({}, React, {
lazy
});
});
var _interopRequireWildcard3 = require("...\\node_modules\\@babel\\runtime/helpers/interopRequireWildcard");
var _interopRequireDefault = require("...\\node_modules\\@babel\\runtime/helpers/interopRequireDefault");
var _interopRequireWildcard2 = _interopRequireDefault(require("...\\node_modules\\@babel\\runtime/helpers/interopRequireWildcard"));
var _objectSpread2 = _interopRequireDefault(require("...\\node_modules\\@babel\\runtime/helpers/objectSpread"));
var React = _interopRequireWildcard3(require("react"));
const React2 = require('react');
...
This may have something to do with create-react-app Jest+Babel setup because I was unable to make jest.dontMock
work incorrectly with vanilla Jest and require
.
Why is static React
import mocked but React2
and the rest aren't? What exactly is going on inside?
How can jest.dontMock
current behaviour be fixed to partially mock a module at top level?
To mock a React component, the most straightforward approach is to use the jest. mock function. You mock the file that exports the component and replace it with a custom implementation. Since a component is basically a function, the mock should also return a function.
mock() doesn't work inside tests, only outside tests.
The Jest library provides the jest. fn() function for creating a “mock” function. An optional implementation function may be passed to jest. fn() to define the mock function's behavior and return value.
There are two ways to mock functions: Either by creating a mock function to use in test code, or writing a manual mock to override a module dependency.
A simple solution would be to mock React.lazy
in the setupTest.js
:
import React from 'react';
import { configure } from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';
configure({ adapter: new Adapter() });
jest.spyOn(React.lazy);
Any subsequent require/imports
of react
will be partially mocked for each test file.
Working example: https://github.com/mattcarlotta/react-lazy-mocked (I don't use the create-react-app
, but jest
can be set up the same way as I have it)
Installation:
git clone [email protected]:mattcarlotta/react-lazy-mocked.git
cd react-lazy-mocked
yarn install
yarn test
root/__tests__/root.test.js
import React from 'react';
import App from '../index.js';
const React2 = require('react');
describe('App', () => {
const wrapper = mount(<App />);
it('renders without errors', () => {
const homeComponent = wrapper.find('.app');
expect(homeComponent).toHaveLength(1);
});
it('should partially mock React module', async () => {
expect(jest.isMockFunction(await require('react').lazy)).toBe(true); // eslint-disable-line global-require
expect(jest.isMockFunction(React)).toBe(false);
expect(jest.isMockFunction(React.lazy)).toBe(true);
expect(jest.isMockFunction(React2)).toBe(false);
expect(jest.isMockFunction(React2.lazy)).toBe(true);
});
it('should no longer be partially mocked within the test file', () => {
React.lazy.mockRestore();
expect(jest.isMockFunction(React.lazy)).toBe(false);
});
});
pages/Home/__tests__/Home.test.js
import React from 'react';
import Home from '../index.js';
describe('Home', () => {
const wrapper = shallow(<Home />);
it('renders without errors', () => {
const homeComponent = wrapper.find('.app');
expect(homeComponent).toHaveLength(1);
});
it('should partially mock React module', async () => {
expect(jest.isMockFunction(React.lazy)).toBe(true);
});
});
Working example: https://github.com/mattcarlotta/named-react-lazy-mocked
Installation:
git clone [email protected]:mattcarlotta/named-react-lazy-mocked.git
cd named-react-lazy-mocked
yarn install
yarn test
utils/__mocks__/react.js
jest.mock('react', () => ({
...require.requireActual('react'),
lazy: jest.fn(),
}));
module.exports = require.requireMock('react');
utils/setup/setupTest.js (optionally, you can add the mocked react
file as a global
jest function so you won't have to write import * as React from 'react'
for every test):
import { JSDOM } from 'jsdom';
import { configure } from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';
// import React from '../__mocks__/react';
configure({ adapter: new Adapter() });
// global.React = React;
root/__tests__/root.test.js
import * as React from 'react';
import App from '../index.js';
const React2 = require('react');
describe('App', () => {
const wrapper = mount(<App />);
it('renders without errors', () => {
const homeComponent = wrapper.find('.app');
expect(homeComponent).toHaveLength(1);
});
it('should partially mock React module', async () => {
expect(jest.isMockFunction(await require('react').lazy)).toBe(true); // eslint-disable-line global-require
expect(jest.isMockFunction(React)).toBe(false);
expect(jest.isMockFunction(React.lazy)).toBe(true);
expect(jest.isMockFunction(React2)).toBe(false);
expect(jest.isMockFunction(React2.lazy)).toBe(true);
});
});
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With