How to Mock non-default import using Jest - javascript

How can I mock a function that is being imported inside the file that contains the function I am testing?
without putting it in the mocks folder.
// FileIWantToTest.js
import { externalFunction } from '../../differentFolder';
export const methodIwantToTest = (x) => { externalFunction(x + 1) }
I need to make sure that externalFunction is called and its called with the correct arguments.
Seems super simple but the documentation doesn't cover how to do this without mocking the module for all the files in the test by putting the mocks in the mocks folder.

The solution: I need to take my jest.mock call outside of any test or any other function because jest needs to hoist it. Also for named exports like in my case I also have to use the following syntax:
jest.mock('../../differentFolder', () => ({
__esModule: true,
externalFunction: jest.fn(),
}));

One of the easiest approaches is to import the library and use jest.spyOn to spy on the method:
import { methodIwantToTest } from './FileIWantToTest';
import * as lib from '../../differentFolder'; // import the library containing externalFunction
test('methodIwantToTest', () => {
const spy = jest.spyOn(lib, 'externalFunction'); // spy on externalFunction
methodIwantToTest(1);
expect(spy).toHaveBeenCalledWith(2); // SUCCESS
});

Related

Get original implementation during jest mock

I'm trying to temporarily mock node-fetch in an ESM module while still retraining the original implementation so I can access a real endpoint's value. However, this errors with "Must use import to load ES Module." I recognize jest support for ESM is still pending - is there any way to have this behavior in a combination of current Node, ES6, and Jest?
worker.ts (dependency):
export default async () => {
const response = await fetch("http://example2.org");
return await response.json()
}
main.test.ts:
import { jest } from "#jest/globals";
jest.mock("node-fetch", () => {
return Promise.resolve({
json: () => Promise.resolve({ myItem: "abc" }),
})
})
import doWork from './worker.js';
import mockedFetch from 'node-fetch';
const originalFetch = jest.requireActual('node-fetch') as any;
test("Ensure mock", async () => {
const result = await doWork();
expect(result.myItem).toStrictEqual("abc");
expect(mockedFetch).toBeCalledTimes(1);
const response = await originalFetch("http://www.example.org");
expect(response.status).toBe(200);
const result2 = await doWork();
expect(result2.myItem).toStrictEqual("abc");
expect(mockedFetch).toBeCalledTimes(2);
});
First, Jest doesn't support jest.mock in ESM module for tests
Please note that we currently don't support jest.mock in a clean way
in ESM, but that is something we intend to add proper support for in
the future. Follow this issue for updates.
This is reasonable because import has different semantic than require. All imports are hoisted and will be evaluated at module load before any module code.
So in your case because you are using jest.mock I assume that your test code are transformed. In this case, if you want to use non "CommonJS" package, you should transform it too. You can change transformIgnorePatterns in jest config to []. It means that all packages from node_modules will go through transform. If it's too aggressive, you can pick specific modules which ignore like this "transformIgnorePatterns": [ "node_modules/(?!(node-fetch))" ] but don't forget about transitive dependencies. ;)
Add/change in jest config
"transformIgnorePatterns": []
jest.mock accept module factory which should returns exported code. In your case, you should write it like this.
jest.mock("node-fetch", () => {
return {
__esModule: true,
default: jest.fn(() =>
Promise.resolve({
json: () => Promise.resolve({ myItem: "abc" }),
})
),
};
});
If you have error
The module factory of jest.mock() is not allowed to reference any out-of-scope variables
just remove jest from import and use it as global variable or import it inside the module factory function
https://github.com/facebook/jest/issues/2567
Change const originalFetch = jest.requireActual('node-fetch') to
const originalFetch = jest.requireActual('node-fetch').default;

How to mock an axios configuration module?

I have a module which configures axios:
// config/axiosConfig.js
import axios from 'axios';
const instance = axios.create({
baseURL: 'http://localhost:8080/api/v1'
});
export default instance;
And a module that uses this to make api calls:
// store/actions.ts
import axiosInstance from 'config/axiosConfig';
export const fetchUsers = () => (dispatch: ThunkDispatch<{}, {}, AnyAction>) => {
dispatch(loading(true));
return axiosInstance.get('/users')
.then(res => {
dispatch(loading(false));
dispatch(fetched(res.data));
})
.catch(err => dispatch(error(true)));
}
...
I want to mock the axios config file to test my api file. I've tried many many ways but nothing works. I thought it would be as simple as
// store/actions.test.ts
import axiosInstance from 'config/axiosConfig';
jest.mock('config/axiosConfig');
axiosConfig.get.mockResolvedValue({users: mockUserList});
...
But I guess that's not how it works.
Edit: The approach in my question works when I put axiosConfig.get.mockResolvedValue({users: mockUserList}); inside of the test, not right under the mock call.
Try this out (Put this at the top of the file or at the top inside beforeAll or beforeEach depending on what you prefer):
jest.mock('config/axiosConfig', () => ({
async get(urlPath) {
return {
users: mockUserList,
};
},
}));
This is a simple mock using a factory function. To use a mock everywhere, jest provides a better way to avoid repeating yourself. You create a __mocks__ directory and inside that, you can create your module and then override many of the builtins. Then, you can get away with just the following code.
// file.test.ts
jest.mock('fs')
// Rest of your testing code below
Take a look at the official docs to learn more about this.
If this doesn't work, then the module resolution setup in jest.config.js and tsconfig.js might be different.

Create manual mock for #material-ui withStyles in React with Jest

I am trying to create manual mocks for the #material-ui/core/styles module, which exports the HOC called withStyles.
Using the inline mock with jest.mock works perfectly, but when I try to move this logic into sharable __mocks__ folder, it does not work anymore.
I removed from the file being tested all the unecessary code, just keeping the two lines creating the issues:
import { withStyles } from '#material-ui/core/styles';
console.log('loaded styles:', withStyles);
export default 'hello';
And the test simplified as well, just to see if the mock is working, is the following:
import test from '../test';
console.log(test);
What I have tried is to create a __mocks__ folder in the root of the project, where I have the node_modules folder.
There I created a first folder called #material-ui and inside it another folder called core.
Inside this folder I have a file called styles.js with the following code:
export const withStyles = 'hello world';
So the structure looks like:
- __mocks__
- #material-ui
- core
- styles.js
- node_modules
This is the code which works, with the mock defined inside the same testing file:
jest.mock('#material-ui/core/styles', () => ({
withStyles: () => Component => props => (
<Component
classes=""
{...props}
/>
),
}));
What is happening with the __mocks__ folder is that, if I do not call any
jest.mock('#material-ui/core/styles');
inside my test file, it uses the real withStyles, so the real module.
No mock at all is being used.
If I use the:
jest.mock('#material-ui/core/styles');
It uses an automatic mock autogenerated by jest (or it looks so), skipping the one I define above.
Just a note about packages, I used CRA for bootstrapping the app and the jest version I currently have is the 20, with the react-scripts version 1.0.17
Thanks to you all for you the help!
Here is how you should use manual mocking in __mocks__/#material-ui/core/styles.js:
// Grab the original exports
import * as Styles from '#material-ui/core/styles';
const mockWithStyles = () => {
console.log('withStyles being mocked'); // this shows that it works
/**
* Note: if you want to mock this return value to be
* different within a test suite then use
* the pattern defined here:
* https://jestjs.io/docs/en/manual-mocks
*/
return () => {}; // your mock function (adjust as needed)
// you can also return the same implementation on certain conditions
// return Styles.makeStyles;
};
module.exports = { ...Styles, withStyles: mockWithStyles };
Here is how I manual mock makeStyles:
// Grab the original exports
// eslint-disable-next-line import/no-extraneous-dependencies
import * as Styles from '#material-ui/core/styles';
import createMuiTheme from '#material-ui/core/styles/createMuiTheme';
import options from '../../../src/themes/options';
const mockMakeStyles = func => {
/**
* Note: if you want to mock this return value to be
* different within a test suite then use
* the pattern defined here:
* https://jestjs.io/docs/en/manual-mocks
*/
/**
* Work around because Shallow rendering does not
* Hook context and some other hook features.
* `makeStyles` accept a function as argument (func)
* and that function accept a theme as argument
* so we can take that same function, passing it as
* parameter to the original makeStyles and
* binding it with our custom theme
*/
const theme = createMuiTheme(options);
return Styles.makeStyles(func.bind(null, theme));
};
module.exports = { ...Styles, makeStyles: mockMakeStyles };
And makeStyles result uses React.useContext, so we have to avoid mocking useContext for makeStyles. Either use mockImplementationOnce if you use React.useContext(...) first in you component, or just filter it out in your test code as:
jest.spyOn(React, 'useContext').mockImplementation(context => {
console.log(context);
// only stub the response if
if (context.displayName === 'MyAppContext') {
return {
auth: {},
lang: 'en',
snackbar: () => {},
};
}
const ActualReact = jest.requireActual('react');
return ActualReact.useContext(context);
});
And on your createContext() call, probably in a store.js, add a displayName (standard), or any other custom property to Identify your context:
const store = React.createContext(initialState);
store.displayName = 'MyAppContext';
The makeStyles context displayName will appear as StylesContext and ThemeContext and their implementation will remain untouched to avoid error.
This fixed all kind of mocking problem with makeStyles. But I haven't use withStyles to know its structure deeply, but similar logic should be applicable.
I created the file src/__mocks__/#material-ui/core/styles.jsx in my project and inside I put:
import * as Styles from '#material-ui/core/styles';
import React from 'react';
const withStyles = () => Component => props => <Component classes={{}} {...props} />;
module.exports = { ...Styles, withStyles };

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.

Jest mock function doesn't work while it was called in the other function

I believe something fundamentally wrong in my understanding for Javascript.
In file abc.js, I have code
export function returnBoolean() {
return true;
}
export function output() {
return returnBoolean();
}
In the test, I do
import * as abc from "../abc";
it("test", () => {
abc.returnBoolean = jest.fn();
abc.returnBoolean.mockReturnValue(false);
expect(abc.returnBoolean()).toBe(false); // This is success
expect(abc.output()).toBe(false); // This failed because return is true
});
I don't know why
abc.output() return is true.
I am really confused. Any thought is really appreciated. Thanks!
output() and returnBoolean() are both in the same file and output() calls returnBoolean() directly.
Mocking the module export for returnBoolean() doesn't have any effect on output() since it is not using the module, it is calling returnBoolean() directly.
Like felixmosh said, moving returnBoolean() to a different module is one way to be able to mock the call to returnBoolean() within output().
The other way is to simply import the module back into itself and use the module to call returnBoolean() within output() like this:
// import the module back into itself
import * as abc from './abc';
export function returnBoolean() {
return true;
}
export function output() {
return abc.returnBoolean(); // use the module to call returnBoolean()
}
With this approach your unit test should work.
Think about it, whenever you import abc module, all the function inside that module are declared, therefore, output function get "bounded" to the original returnBoolean.
Your mock won't apply on the original function.
You have 2 choices:
If the returnBoolean can be on a separate module, you will able to use jest's mocking mechnisem.
If you able to change the interface of output method so it will be able to get the returnBoolean from outside of the module.
Then you will be able to pass to it, jest.fn() and make your expectation on it.

Categories