Jest mocking module implementation with .mockImplemetation - javascript

Can anyone help here. I am really frustrated with how the mockImplementation works.
So, first of all I am using jest for node testing. I am using the commonjs modules. what I wanna do is that I am trying to mock a module using mockImplementation() and change its implementation between different tests according to this documentation here: https://jestjs.io/docs/en/es6-class-mocks#replacing-the-mock-using-mockimplementation-docs-en-mock-function-api-mockfnmockimplementationfn-or-mockimplementationonce-docs-en-mock-function-api-mockfnmockimplementationoncefn.
My code look like this:
const exportBigQueryTableModule =require('../repository/exportBigQueryTable')
jest.mock('../repository/exportBigQueryTable')
describe('deleting files on table export fail', () => {
mockExportBigQueryTable = jest
.fn(() => Promise.resolve())
.mockResolvedValueOnce()
.mockRejectedValueOnce(new Error('Could not export table'))
exportBigQueryTableModule.mockImplementation(mockExportBigQueryTable)
it(' should do some test', () => {})
})
The problem here is that looks like that this line jest.mock('../repository/exportBigQueryTable') create for me a default mock kind of jest.fn() and the module is always loaded with that default function. So the mock function that I did provide on the test using the mockImplementation never overrides the previous one, I do not get what is the problem here. Why the same exmaple on the official documentation works the only difference is that it uses es6 modules on the doc sample.
I am not sure if I am missing something here.

Related

Why is Jest still requiring a mocked module?

I am mocking a module using Jest because it contains code that shouldn't run in the test. However I can see from the output that code in the module is being run.
// foo.js
console.log('Hello')
// test.js
jest.mock('./foo')
const foo = require('./foo')
test.todo('write some tests')
Console output
PASS test.js
✎ todo 1 test
console.log foo.js:1
Hello
What's up with that?
This has tripped me up a couple of times.
If you don't provide a mock implementation to jest.mock it will return an object which mirrors the exports of the mocked module but with every function replaced with a mock jest.fn(). This is pretty neat as it is often what you want. But in order to determine the exports of the module, it must first require it. This is what is causing the console.log to be run.
Two possible solutions:
Don't run code in the top level of the module: instead export a function which runs the code.
Provide your own mock implementation so it doesn't need to introspect the module jest.mock('./foo', () => {})

How to make Jest test client-side scripts?

I've got some client-side code that I'd like to test with Jest. Let's narrow it down to this reproducible bit:
// fetch-example.js
const fetchExample = () => fetch('http://example.org/');
module.exports = fetchExample
Since Jest runs tests via Node and Node doesn't support fetch, the following test script
// fetch-example.test.js
const fetchExample = require('./fetch-example')
test('fetch resolves to something', () => {
expect.assertions(1);
return fetchExample().then(() => expect(true).toBe(true));
});
fails with ReferenceError: fetch is not defined. No surprises, but... fetchExample works fine in a browser and I'd like Jest to take this into account and mark the test as passed.
Is there any way to make this work? Is there any parameter I can pass to Jest to include browser functions like fetch?
NB: Jest's browser configuration entry does not make it work.

Jest mock module for single import

When using jest with ES6 modules and babel-jest, all the jest.mock calls are hoisted.
Let's say I'd like to mock fs module for the tested class, but preserve the original implementation for the rest of the modules (for example some utils that I use during the test).
Consider the following example:
class UnderTest {
someFunction(){
fs.existsSync('blah');
}
}
class TestUtility {
someOtherFunction(){
fs.existsSync('blahblah');
}
}
the test:
it('Should test someFunction with mocked fs while using TestUtility'', () => {
testUtility.someOtherFunction(); // Should work as expected
underTest.someFunction(); // Should work with mock implementation of 'fs'
})
Now, one would expect that with the following approach the fs module will be mocked for UnderTest but not for TestUtility.
import {TestUtility} from './test-utility';
jest.mock('fs');
import {UnderTest } from './under-test';
However, due to the hoisting, fs module will be mocked for all the modules (which is undesirable).
Is there any way to achieve the described behavior?
To opt out from mocking a module in a test jest.doMock(moduleName, factory, options) and jest.dontMock(moduleName) should be used.
jest.doMock(moduleName, factory, options)
When using babel-jest, calls to mock will automatically be hoisted to the top of the code block. Use this method if you want to explicitly avoid this behavior.
jest.dontMock(moduleName)
When using babel-jest, calls to unmock will automatically be hoisted to the top of the code block. Use this method if you want to explicitly avoid this behavior.
So in your case I would try something like
beforeEach(() => {
jest.resetModules();
});
it('Should test someFunction with mocked fs while using TestUtility'', () => {
jest.dontMock('fs');
testUtility.someOtherFunction(); // Should work as expected
jest.doMock('fs', () => {
return ... // return your fs mock implementation;
});
underTest.someFunction(); // Should work with mock implementation of 'fs'
})
You should probably use jest's requireActual:
const fs = jest.requireActual('fs'); // Unmockable version of jest
class TestUtility {
someOtherFunction(){
fs.existsSync('blahblah');
}
}
From the documentation:
Jest allows you to mock out whole modules in your tests, which can be useful for testing 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.

Trying to mock just certain named exports (e.g. PushNotificationsIOS) in react-native with jest

Given I have an implementations files that looks something :
import ReactNative, { PushNotificationIOS, AsyncStorage } from 'react-native';
export function tryNotify() {
PushNotificationIOS.addEventListener('register', token => {
callback(token);
});
PushNotificationIOS.requestPermissions();
}
export function trySave(token) {
AsyncStorage.setItem('blah', token);
}
So if I want to write a test that spies on:
PushNotificationIOS.addEventListener.
However, I can't work out how to mock it, because as soon as I mock react-native...
describe('notify()', () => {
let generator;
beforeAll(() => {
jest.mock('react-native', () => ({
PushNotificationIOS: {
addEventListener: jest.fn(),
requestPermission: jest.fn(),
},
}));
});
afterAll(() => {
jest.unmock('react-native');
});
// No tests yet!
});
...I start getting the following error in my test:
Invariant Violation: Navigator is deprecated and has been removed from this package. It can now be installed and imported from `react-native-deprecated-custom-components` instead of `react-native`. Learn about alternative navigation solutions at http://facebook.github.io/react-native/docs/navigation.html
My best guess is I'm interfering with the inbuilt react-native mocks that jest provides:
The Jest preset built into react-native comes with a few defaults mocks that are applied on a react-native repository.
-jest docs
But I don't know where to look for to confirm this.
Does anyone have experience with this?
Thanks!
Edit
So I have two solutions:
AsyncStorage: the answer below works, as does this SO answer
PushNotificationsIOS: the answer below does not work for me, but this SO answer did
You can't jest.mock('react-native',... because react-native does some slightly nasty things with its exports, such that they can't be imported en-masse by jest or anything else.
You'll need to bypass this by targeting the module more directly:
jest.mock('react-native/Libraries/PushNotificationIOS', () => {})

Mock doesn't work, while reassignment does

I have the following Jest/Enzyme test:
const mockCheckMyFunctionality = jest.fn();
jest.mock('../modules/MyFunctionality', () => ({
checkMyFunctionality: mockCheckMyFunctionality
}));
const wrapper = shallow(
<App initialProps={mockInitialProps} />
);
expect(mockCheckMyFunctionality).toHaveBeenCalledTimes(1);
This will fail with TypeError: Cannot read property 'onNextTick' of undefined. The error message itself is not relevant, but it just shows that the real MyFunctionality.checkMyFunctionality is called instead of mockCheckMyFunctionality.
However, if I replace:
jest.mock('../modules/MyFunctionality', () => ({
checkMyFunctionality: mockCheckMyFunctionality
}));
With:
MyFunctionality.checkMyFunctionality = mockCheckMyFunctionality;
The test will pass, showing that mockCheckMyFunctionality is actually called. However, this is hacky and fails EsLint checking.
The method I am testing is just this:
setupMyFunctionality() {
checkMyFunctionality(this.props.something);
}
How can I modify the mocking such that it is visible inside App?
Reassignment seems to work but mocking doesn't.
Maybe you forgot to create a manual mock for the MyFuncionality module.
As in the documentation for mocking a module:
Manual mocks are defined by writing a module in a __mocks__/
subdirectory immediately adjacent to the module. For example, to mock a module called user in the models directory, create a file called user.js and put it in the models/__mocks__ directory.
...
When a manual mock exists for a given module, Jest's module system will use that module when explicitly calling jest.mock('moduleName').
If that's the case, you can create a folder named __mocks__ adjacent to the MyFunctionality file, then create a MyFunctionality file within that folder, containing the mock implementation as in the code snippet, and explicitly call the mock by calling jest.mock('../modules/MyFunctionality') before the tests.
Another solution, which I started using in my projects, is passing the dependencies as props to the components. To implement that you could change the App component and pass the MyFunctionality as props, thus in the above test you would need just to pass the mock implemented as props, avoiding the necessity of creating __mocks__/ folder to accomplish the test.

Categories