How to spy on a default exported function with Jest? - reactjs

Suppose I have a simple file exporting a default function:
// UniqueIdGenerator.js
const uniqueIdGenerator = () => Math.random().toString(36).substring(2, 8);
export default uniqueIdGenerator;
Which I would use like this:
import uniqueIdGenerator from './UniqueIdGenerator';
// ...
uniqueIdGenerator();
I want to assert in my test that this method was called while keeping the original functionality. I'd do that with jest.spyOn however, it requires an object as well as a function name as parameters. How can you do this in a clean way? There's a similar GitHub issue for jasmine for anyone interested.

I ended up ditching the default export:
// UniqueIdGenerator.js
export const uniqueIdGenerator = () => Math.random().toString(36).substring(2, 8);
And then I could use and spy it like this:
import * as UniqueIdGenerator from './UniqueIdGenerator';
// ...
const spy = jest.spyOn(UniqueIdGenerator, 'uniqueIdGenerator');
Some recommend wrapping them in a const object, and exporting that. I suppose you can also use a class for wrapping.
However, if you can't modify the class there's still a (not-so-nice) solution:
import * as UniqueIdGenerator from './UniqueIdGenerator';
// ...
const spy = jest.spyOn(UniqueIdGenerator, 'default');

one could also mock the import and pass the original implementation as mock implementation, like:
import uniqueIdGenerator from './UniqueIdGenerator'; // this import is a mock already
jest.mock('./UniqueIdGenerator.js', () => {
const original = jest. requireActual('./UniqueIdGenerator')
return {
__esModule: true,
default: jest.fn(original.default)
}
})
test(() => {
expect(uniqueIdGenerator).toHaveBeenCalled()
})

Here is a way of doing it for a default export without modifying the import (or even needing an import in the test at all):
const actual = jest.requireActual("./UniqueIdGenerator");
const spy = jest.spyOn(actual, "default");

In some cases you have to mock the import to be able to spy the default export:
import * as fetch from 'node-fetch'
jest.mock('node-fetch', () => ({
default: jest.fn(),
}))
jest.spyOn(fetch, 'default')

Mock only the default export, or any other export, but keep remaining exports in module as original:
import myDefault, { myFunc, notMocked } from "./myModule";
jest.mock("./myModule", () => {
const original = jest.requireActual("./myModule");
return {
__esModule: true,
...original,
default: jest.fn(),
myFunc: jest.fn()
}
});
describe('my description', () => {
it('my test', () => {
myFunc();
myDefault();
expect(myFunct).toHaveBeenCalled();
expect(myDefault).toHaveBeenCalled();
myDefault.mockImplementation(() => 5);
expect(myDefault()).toBe(5);
expect(notMocked()).toBe("i'm not mocked!");
})
});

Use 'default' as the second argument in spyOn function.
import * as MyHelperMethod from '../myHelperMethod';
jest.spyOn(MyHelperMethod, 'default');

What worked for me was a combination of the answer from Janne Annala and OP's own solution. All I wanted to test was that the helper method was called with the correct parameters as I had already written a test for the helper method and it didn't have any bearing on my subsequent test:
// myHelperMethod.js
export const myHelperMethod = (param1, param2) => { // do something with the params };
// someOtherFileUsingMyHelperMethod.js
import * as MyHelperMethod from '../myHelperMethod';
jest.mock('../myHelperMethod', () => ({
myHelperMethod: jest.fn(),
}));
let myHelperMethodSpy = jest.spyOn(MyHelperMethod, 'myHelperMethod');
// ...
// some setup
// ...
test(() => {
expect(myHelperMethodSpy).toHaveBeenCalledWith(param1, param2);
});

Here it is even simpler.
Mock your exported module 'addDelay' (has the sleep function in it) using jest.
const { sleep } = require('../../src/utils/addDelay');
jest.mock('../../src/utils/addDelay', () => {
const delay = jest.requireActual('../../src/utils/addDelay');
return {
...delay,
sleep: jest.fn(),
};});
And the test is as follows and check if sleep function was called with 1 sec as in arg.
test("Should delay 1 second if Okta user has no IDxM Roles", async () => {
// GIVEN
const MockSleep = sleep;
// WHEN
await getUser(req, res);
// THEN
expect(MockSleep).toHaveBeenCalledWith(1000);// sleep(1000): 1sec
});

I know I'm late to the party but I recently had this problem and wanted to share my solution as well ... though it seems a bit more unconventional but could be tweaked by someone with better knowledge.
I happen to have a file with the function that I would like to spy on.
// /foo/ModuleToBeMocked.ts
const fnToSpyOn = () => ...;
export default { fnToSpyOn }
This is then imported into a parent file that would bring, and export, alike functions. Sort of like a classification.
// /parent.ts
import fnToSpyOn from './foo/ModuleToBeMocked';
import someOtherFn from './foo/SomeOtherModule';
...
export { fnToSpyOn, someOtherFn, ... };
And this is how I test the fnToSpyOn
// /foo/ModuleToBeMocked.test.ts
import { ModuleToBeMocked } from '../parent';
const fnToSpyOnSpu = jest.spyOn(ModuleToBeMocked, 'fnToSpyOn');

Related

Jest mock redux toolkit actions/store files

I'm using Redux toolkit in a React.js app, and I extracted my logic as below:
Store.tsx
const history = createHashHistory();
const middleware = [
...getDefaultMiddleware().concat(routerMiddleware(history)),
];
const Store = configureStore({
reducer: {
router: connectRouter(history) as Reducer<any, any>,
/* my reducers */
}, middleware
});
MySlice.tsx
import {createSlice} from '#reduxjs/toolkit';
const initialState = {
/* content of my state */
};
const mySlice = createSlice(
{
name: "myState",
initialState,
reducers: {
myAction: (state: MyState) => {
// Whatever here...
}
},
extraReducers: builder => {
/* My extra reducers */
}
});
export const MySlice = mySlice;
And then I have my function:
MySuperFunc.tsx
export const superFunc = () => {
/* content of my function */
const {myAction} = MySlice.actions;
Store.dispatch(myAction({my: 'payload'}));
};
I would like to unit test it with Jest. I want to mock the content of my Store/MySlice because the configureStore & createSlice doing extra logics and seems to require some configuration.
I'm a little lost from React.js best practices & documentation regarding mock, setMock and spyOn.
superFunc.spec.ts
const dispatchMockFn = jest.fn();
// Sol: 1
jest.mock('<< path >>/Store', () => ({dispatch: dispatchMockFn )});
// Sol: 2
jest.setMock('<< path >>/Store', {dispatch: dispatchMockFn});
// Sol: 3
jest.spyOn(Store, 'dispatch');
describe('superFunc', () => {
it('should call store', () => {
superFunc();
return expect(dispatchMockFn).toHaveBeenCalledWith(/* results of myAction({my: 'payload'}) */);
});
});
The problem I faced some error by code executed in the Store:
Seems to be normal because I used "Store" which is not exporting only an object, we have some extra code inside (createHistory etc.).
I searched a solution to mock entirely a module, that's why I try setMock/mock, it change a little bit the error but know it complaining regarding MySlice (extraReducers) saying that my promises.fulfilled/rejected are not defined etc.
But I don't want to go deep inside the Store/Slice config, I just want to mock the file content.
Any idea/recommendation?
I found the solution:
The jest.mock factory must be adapted from your export (export default VS. export const = {...})
Example 1:
MySuperFunc.tsx (with export default)
export default ({superFunc: () => 'hello world'});
Unit test:
import superFunc from './MySuperFunc';
jest.mock('./MySuperFunc', () => ({superFunc: jest.fn()});
it('should return toto', () => {
(MySuperFunc.superFunc as any).mockReturnValue('toto');
return expect(MySuperFunc.superFunc).toHaveBeenCalledWith('toto');
});
But, in the example 2 if you change your export by:
// MySuperFunc.tsx
export const MyVariable = {superFunc: () => 'hello world'});
Your jest.mock must be adapted as:
jest.mock('./MySuperFunc', () => ({MyVariable: {superFunc: jest.fn()}});
So when I re-take my question, I have 2 choices:
I can change my export in my slice file by using an export default
Or I adapt my jest.mock including {MySlice: {actions: {myAction: () => '...'}}}
My apologies, this topic is clearly not related to "redux toolkit" but more on how you deals with your export content from your typescript files.

ReacJS destruct function

I learnig reactjs (I'm from Angular, C#) and I broking my head with this....have this lib
const libA=()=>{
const fnA = () => {
return ...
}
const fnB = () => {
return ...
}
const fnC = () => {
return ...
}
return [fnA, fnB, fbC]
}
export default libA
And I consume libA this way
import { libA } from '../myLib.js'
const compB=()=>{
const [fnA, fnB, fnC] = myLib()
....
fnA()...
}
My problem is if I change order const [fnB, fnA, fnC ] is seted fns to wrong name (fnB=fnA,...). There is any that I can getr corretly fn only by name or I need put correctly name var order? My pain is if I have 10 fns I wiil need declare all vars since I use only 1, 2, or alterante vars from lib?
basically i want same thing like old way
function myLib(){
fnA: function () {return...},
fnB: function () {return...},
fnC: function () {return...},
}
myLib().fnA().....
thks
Firstly, you exported libA as a default export so you should not import them as named imports
Eg: import libA from '../myLib.js' instead of import { libA } from '../myLib.js'.
Secondly, deconstructing arrays in JS mean that sequence/order of the array items are maintained. You cannot just change the order of elements when destructuring an array.
If you want to change the order of callbacks, you should export an object instead of an array in myLib.js
const libA=()=>{
const fnA = () => {
return ...
}
const fnB = () => {
return ...
}
const fnC = () => {
return ...
}
return {fnA, fnB, fbC}
}
export default libA
And you consume the libA like
const compB=()=>{
const {fnA, fnB, fnC} = myLib()
....
fnA()...
}
PS: you have 10, 20 or more functions in that export.
You don't need to destructure the import, simply call them by .
import libA from '../myLib.js'
const compB=()=>{
....
libA()?.fnA() // conditionally call the fnA only if libA returns the function
}
I think you don't need to scope all these libs inside a function to make it work as a module. Instead take advantage of the module object itself:
// libA.js
export const fnA = () => {
return ...
}
export const fnB = () => {
return ...
}
export const fnC = () => {
return ...
}
and in the other file
// anotherFile.js
import {fnA, fnB, fnC} from '../libA.js'
libA()
libB()
{...}
Other guy already mentioned that you probably not need destructure at all so direct import would be the best solution here.
But just in sake of completeness: if we have hook(myLib does not look as a hook, but let's assume) and it returns array and we don't want to put every array's item into separate variable - say, because we don't use them all, we have two alternatives.
First, we can just avoid destructuring:
myLibCallbacks = useMyLib();
...
myLibCallbacks[4]('a', 1, 2);
Sure, does not look good with this numeric indexes. Alternative is skipping elements during destructuring:
const [fnA,,,,fnE] = useMyLib();
Sure, if we can change useMyLib return value to be an object instead of array - we definitely should do that.

Create helper function for a (click handler) function to reuse in multiple React components

For a 'back' button I've created below (onClick) handler function in my React app.
const { length: historyLength, goBack, replace } = useHistory();
const handleBack = () => {
if (historyLength > 2) {
goBack();
} else {
// History length is 2 by default when nothing is pushed to history yet
// https://stackoverflow.com/questions/9564041/why-history-length-is-2-for-the-first-page
replace(HomePage);
}
};
Then I am passing the onClick handler to my child component like: <Button onClick={handleBack}/>
I am using this handleBack function in multiple places in my React app. Is it a good approach make it e.g. a helper function and how exactly?
I also don't see any issue with the code or using it as a utility callback.
Is it a good approach make it e.g. a helper function and how exactly?
Anytime you can make your code more DRY (Don't Repeat Yourself) it's generally a good thing. My personal rule-of-thumb is if I've written the same utility code a third time I'll spend a bit of time to refactor it into a common utility (and unit test!!).
I might suggest creating a custom hook to return the back handler.
Example:
import { useHistory } from 'react-router-dom';
const useBackHandler = () => {
const history = useHistory();
const handleBack = React.useCallback(() => {
const { length: historyLength, goBack, replace } = history;
if (historyLength > 2) {
goBack();
} else {
replace(HomePage);
}
}, []);
return handleBack;
};
export default useBackHandler;
Now you have a single hook to import and use.
import useBackHandler from '../path/to/useBackHandler';
...
const backHandler = useBackHandler();
...
<button type="button" onClick={backHandler}>Back?</button>
If you are needing this function in older class components, then you'll need a way to inject the handleBack as a prop. For this you can create a Higher Order Component.
Example:
import useBackHandler from '../path/to/useBackHandler';
const withBackHandler = Component => props => {
const backHandler = useBackHandler();
return <Component {...props} backHandler={backHandler} />;
};
export default withBackHandler;
To use, import withBackHandler and decorate a React component and access props.backHandler.
import withBackHandler from '../path/to/withBackHandler';
class MyComponent extends React.Component {
...
someFunction = () => {
...
this.props.backHandler();
}
...
}
export default withBackHandler(MyComponent);
#meez
Don't see why this wouldn't work. Just a couple of things: (a) I would add the event argument and e.preventDefault() within the function and (b) would be careful of the function name you are passing on the onClick property of your button: handleBackClick !== handleBack, you'll get an ReferenceError because of an undefined function.
Additionally, I also noticed that this can be achieved with native browser functions. Here's a snippet:
const { length: historyLength, back } = window.history;
const { replace } = window.location;
const handleBack = (e) => {
e.preventDefault();
if (historyLength > 2) {
back();
} else {
replace('homepageUrl');
}
};

How do I write this test for complete coverage?

I am new to React development and am studying testing with Jest and React Testing Library (RTL).
But I'm having difficulty doing the complete coverage of the component below:
import {
CustomCardActions,
CustomCardHeader,
} from '#Custom/react';
import React from 'react';
import {
PortalAccessButton,
PortalAccessContext,
PortalAccessInternalCard,
PortalAccessTitle,
} from './styles';
interface PortalAccessCard {
children: React.ReactNode
buttonText: string;
hrefLink: string;
}
export const redirectToUrl = (hrefLink: string) => {
window.open(hrefLink, '_self');
};
const PortalAccessCard = (props: PortalAccessCard) => {
const { children, buttonText, hrefLink } = props;
return (
<PortalAccessContext inverse>
<PortalAccessInternalCard>
<CustomCardHeader>
<PortalAccessTitle variant="heading-4">
{children}
</PortalAccessTitle>
</CustomCardHeader>
<CustomCardActions>
<PortalAccessButton onCustomClick={() => redirectToUrl(hrefLink)}>
{buttonText}
</PortalAccessButton>
</CustomCardActions>
</PortalAccessInternalCard>
</PortalAccessContext>
);
};
export default React.memo(PortalAccessCard);
There are two details here:
1- I exported the "redirectToUrl" method to be able to test it. I can't say if there's a better way out, but maybe the second question solves this one.
2- When I check the coverage report it says that this part () => redirectToUrl(hrefLink) has not been tested, but it is basically the pointer to the method I exported above.
My test looks like this:
import { render, RenderResult } from '#testing-library/react';
import userEvent from '#testing-library/user-event';
import PortalAccessCard from '.';
import * as PortalAccessCardComponent from '.';
describe('PortalAccessCard', () => {
let renderResult: RenderResult;
const hrefLink = '#';
beforeEach(() => {
renderResult = render(
<PortalAccessCard
buttonText="Texto do botão"
hrefLink={hrefLink}
>
Texto interno PortalAccessCard.
</PortalAccessCard>,
);
});
it('should call onCustomClick and redirectToUrl', async () => {
window.open = jest.fn();
jest.spyOn(PortalAccessCardComponent, 'redirectToUrl');
const onCustomClick = jest.fn(() => PortalAccessCardComponent.redirectToUrl(hrefLink));
const CustomButtonElement = renderResult.container.getElementsByTagName('Custom-button')[0];
CustomButtonElement.onclick = onCustomClick;
await userEvent.click(CustomButtonElement);
expect(onCustomClick).toBeCalledTimes(1);
expect(PortalAccessCardComponent.redirectToUrl).toBeCalledTimes(1);
});
});
What can I do to make the test call of the onCustomClick event call the redirectToUrl method so that Jest understands that this snippet has been tested?
Not sure which exactly line is not covered... Though, toBeCalledTimes is a sign of bad test expectation, so try to append to the very bottom line:
expect(PortalAccessCardComponent.redirectToUrl).toBeCalledWith(hrefLink);
It's better to test for the side effect you want (opening a window). redirectToUrl is an implementation detail. I think you're making this much harder than it needs to be.
Spy on window.open, click the item, check the spy. I think that's all you need.
jest.spyOn(window, 'open')
const CustomButtonElement = renderResult.container.getElementsByTagName('Custom-button')[0];
await userEvent.click(CustomButtonElement);
// or maybe: getByRole('something...').click()
expect(window.open).toHaveBeenCallWith('#', '_self')

how to change jest mock function return value in each test?

I have a mock module like this in my component test file
jest.mock('../../../magic/index', () => ({
navigationEnabled: () => true,
guidanceEnabled: () => true
}));
these functions will be called in render function of my component to hide and show some specific feature.
I want to take a snapshot on different combinations of the return value of those mock functions.
for suppose I have a test case like this
it('RowListItem should not render navigation and guidance options', () => {
const wrapper = shallow(
<RowListItem type="regularList" {...props} />
);
expect(enzymeToJson(wrapper)).toMatchSnapshot();
});
to run this test case I want to change the mock module functions return values to false like this dynamically
jest.mock('../../../magic/index', () => ({
navigationEnabled: () => false,
guidanceEnabled: () => false
}));
because i am importing RowListItem component already once so my mock module wont re import again. so it wont change. how can i solve this ?
You can mock the module so it returns spies and import it into your test:
import {navigationEnabled, guidanceEnabled} from '../../../magic/index'
jest.mock('../../../magic/index', () => ({
navigationEnabled: jest.fn(),
guidanceEnabled: jest.fn()
}));
Then later on you can change the actual implementation using mockImplementation
navigationEnabled.mockImplementation(()=> true)
//or
navigationEnabled.mockReturnValueOnce(true);
and in the next test
navigationEnabled.mockImplementation(()=> false)
//or
navigationEnabled.mockReturnValueOnce(false);
what you want to do is
import { navigationEnabled, guidanceEnabled } from '../../../magic/index';
jest.mock('../../../magic/index', () => ({
navigationEnabled: jest.fn(),
guidanceEnabled: jest.fn()
}));
describe('test suite', () => {
it('every test', () => {
navigationEnabled.mockReturnValueOnce(value);
guidanceEnabled.mockReturnValueOnce(value);
});
});
you can look more about these functions here =>https://facebook.github.io/jest/docs/mock-functions.html#mock-return-values
I had a hard time getting the accepted answers to work - my equivalents of navigationEnabled and guidanceEnabled were undefined when I tried to call mockReturnValueOnce on them.
Here's what I had to do:
In ../../../magic/__mocks__/index.js:
export const navigationEnabled = jest.fn();
export const guidanceEnabled = jest.fn();
in my index.test.js file:
jest.mock('../../../magic/index');
import { navigationEnabled, guidanceEnabled } from '../../../magic/index';
import { functionThatReturnsValueOfNavigationEnabled } from 'moduleToTest';
it('is able to mock', () => {
navigationEnabled.mockReturnValueOnce(true);
guidanceEnabled.mockReturnValueOnce(true);
expect(functionThatReturnsValueOfNavigationEnabled()).toBe(true);
});

Resources