// myModule.test.js
jest.mock('./myModule.js', () => (
{
...(jest.requireActual('./myModule.js')),
otherFn: jest.fn()
}
))
import { otherFn } from './myModule.js'
describe('test category', () => {
it('tests something about otherFn', () => {
otherFn.mockReturnValue('foo')
expect(otherFn()).toBe('foo')
})
})
// myModule.js
export function otherFn() {
console.log('do something');
}
export function testFn() {
otherFn();
// do other things
}
As shown above, it exports some named functions and importantly testFn uses otherFn.
my unit test for testFn, I want to mock the otherFn function because I don't want errors in otherFn to affect my unit test for testFn. My issue is that I'm not sure the best way to do that:
Jest allows you to mock out whole modules in your tests, which can be useful for testing if your code is calling functions from that module correctly. However, sometimes you may want to use parts of a mocked module in your test file, in which case you want to access the original implementation, rather than a mocked version.
Consider writing a test case for this createUser function:
// createUser.js
import fetch from 'node-fetch';
export const createUser = async () => {
const response = await fetch('http://website.com/users', {method: 'POST'});
const userId = await response.text();
return userId;
};
Copy Your test will want to mock the fetch function so that we can be sure that it gets called without actually making the network request. However, you'll also need to mock the return value of fetch with a Response (wrapped in a Promise), as our function uses it to grab the created user's ID. So you might initially try writing a test like this:
jest.mock('node-fetch');
import fetch, {Response} from 'node-fetch';
import {createUser} from './createUser';
test('createUser calls fetch with the right args and returns the user id', async () => {
fetch.mockReturnValue(Promise.resolve(new Response('4')));
const userId = await createUser();
expect(fetch).toHaveBeenCalledTimes(1);
expect(fetch).toHaveBeenCalledWith('http://website.com/users', {
method: 'POST',
});
expect(userId).toBe('4');
});
However, if you ran that test you would find that the createUser function would fail, throwing the error: TypeError: response.text is not a function. This is because the Response class you've imported from node-fetch has been mocked (due to the jest.mock call at the top of the test file) so it no longer behaves the way it should.
To get around problems like this, Jest provides the jest.requireActual helper. To make the above test work, make the following change to the imports in the test file:
// BEFORE
jest.mock('node-fetch');
import fetch, {Response} from 'node-fetch';
Copy
// AFTER
jest.mock('node-fetch');
import fetch from 'node-fetch';
const {Response} = jest.requireActual('node-fetch');
Copy This allows your test file to import the actual Response object from node-fetch, rather than a mocked version. This means the test will now pass correctly.
//EventTracker.js
export const trackEventName = (
eventType,
eventTitle
) => `Track Event - ${eventType} - ${eventTitle}`;
export function trackEvent(eventName){
//do something not important
}
And then we have a function that we want to be tested:
//ClickBoxTracking.js
import React from 'react';
import { trackEventName, trackEvent } from '../../EventTracker';
import {ClickBox} from "../ClickBox"
export const ClickBoxWithTracking = ({
trackingTitle,
...props
}) => {
const tracking = () => {
trackEvent(trackEventName('ClickBox', trackingTitle));
};
return (<ClickBox tracking={tracking} {...props} />);
};
Finally this is how we test it :
import { trackEvent } from '../../EventTracker';//1 先引入
jest.mock('../../EventTracker', () => {
const actualTracker = jest.requireActual('../../EventTracker');
return {
...actualTracker,
trackEvent: jest.fn(),
};
});
const MockTrackEvent = trackEvent;
import React from 'react';
import { mount, ReactWrapper } from 'enzyme';
import { ClickBoxWithTracking } from './ClickBoxTracking';
import { trackEvent } from '../../EventTracker';//1 先引入
jest.mock('../../EventTracker', () => {
const actualTracker = jest.requireActual('../../EventTracker');
return {
...actualTracker,
trackEvent: jest.fn(),
};
});
describe('ClickBoxWithTracking', () => {
const MockTrackEvent = trackEvent;
let wrapper;
beforeEach(() => {
wrapper = mount(<ClickBoxWithTracking trackingTitle="Current tick option">Current tick option...</ClickBoxWithTracking>);
});
it('should call trackEvent when the clickBox is ticked', () => {
//do something to simulate the click
expect(MockTrackEvent).toHaveBeenCalledWith('ClickBox - Current tick option');
});
});
Notice when we do the following, we are returning { trackEventName, trackEvent } and then trackEvent: jest.fn() overwrite the trackeEvent returned from the module.
jest.mock('../../EventTracker', () => {
const actualTracker = jest.requireActual('../../EventTracker');
return {
...actualTracker,
trackEvent: jest.fn(),
};
});
The reason we want to do this is because when we do the mounting: mount(Current tick option...); There is this method get triggered inside the component:
const tracking = () => {
trackEvent(trackEventName('ClickBox', trackingTitle));
};
We still want to call trackEventName but just want to mock trackEvent . So we require the real module thereby requiring the real trackEventName and overwrite the latter with the mocked trackEvent . Of course you can mock trackEventName as well. But in this test, we don’t want to add another layer of complexity, and would like to actually test if the trackEventName can be passed in to trackEvent and rendered by ClickBoxWithTracking . You can even more explicitly requiring the real trackEventName like this after you require the module.
jest.mock('../../EventTracker', () => {
const actualTracker = jest.requireActual('../../EventTracker');
return {
...actualTracker,
trackEvent: jest.fn(),
};
});
const { trackEventName } = require('../../EventTracker');
```js
//do something with trackingEventName