How do you mock isPlatform from #ionic/core - reactjs

Have a React Ionic app where I need to determine if the app is running on an iOS device. I've done that by importing isPlatform.
import { isPlatform } from "#ionic/core";
const isIOS = isPlatform("ios");
I've tried the following and when isPlatform is called it still returns false.
jest.mock("#ionic/core", () => ({
isPlatform: () => {
return true;
}
}));
How do I mock isPlatform in my unit test using jest so it returns true?
Figured it out. I needed to mock ionic/core in order for it to work.
jest.mock("#ionic/core");
import * as Ionic from '#ionic/core';
(Ionic as any).isPlatform = jest.fn(() => true);

If component only uses isFlatform in #ionic/core you can mock one function isFlatform:
jest.mock("#ionic/core", () => ({
isPlatform: () => true,
}));
but when component use another function and you only want to mock isFlatform you can use:
jest.mock("#ionic/core", () => ({
...jest.requireActual("#ionic/core"),
isPlatform: () => true,
}));

Similar to the previous responses but using a slightly more flexible approach to be able to mock the result based on what we want to test in each scenario. In our app we're using Angular but this approach should be working as well for React.
The idea is to define a mockIsPlatform() function:
// Note: on our app, we're importing the `isPlatform` helper method
// from `#ionic/angular` but that's not really important.
let mockIsPlatform: (key: string) => boolean;
jest.mock('#ionic/angular', () => ({
...(jest.requireActual('#ionic/angular') as object),
isPlatform: (key: string) => mockIsPlatform(key),
}));
Our service has some methods that use the isPlatform() method behind the scenes:
public isIos(): boolean {
return isPlatform('ios');
}
public isAndroid(): boolean {
return isPlatform('android');
}
So we can now test those methods like this:
test('detect platform', () => {
// Simulate ios platform
mockIsPlatform = (key) => key === 'ios';
expect(myServiceInstance.isIos()).toBeTruthy();
expect(myServiceInstance.isAndroid()).toBeFalsy();
// Simulate android platform
mockIsPlatform = (key) => key === 'android';
expect(myServiceInstance.isIos()).toBeFalsy();
expect(myServiceInstance.isAndroid()).toBeTruthy();
})

Related

React Testing Library Unit Test Case: Unable to find node on an unmounted component

I'm having issue with React Unit test cases.
React: v18.2
Node v18.8
Created custom function to render component with ReactIntl. If we use custom component in same file in two different test cases, the second test is failing with below error.
Unable to find node on an unmounted component.
at findCurrentFiberUsingSlowPath (node_modules/react-dom/cjs/react-dom.development.js:4552:13)
at findCurrentHostFiber (node_modules/react-dom/cjs/react-dom.development.js:4703:23)
at findHostInstanceWithWarning (node_modules/react-dom/cjs/react-dom.development.js:28745:21)
at Object.findDOMNode (node_modules/react-dom/cjs/react-dom.development.js:29645:12)
at Transition.performEnter (node_modules/react-transition-group/cjs/Transition.js:280:71)
at node_modules/react-transition-group/cjs/Transition.js:259:27
If I run in different files or test case with setTimeout it is working as expected and there is no error. Please find the other configs below. It is failing even it is same test case.
setUpIntlConfig();
beforeAll(() => server.listen());
afterEach(() => {
server.resetHandlers();
});
afterAll(() => {
jest.clearAllMocks();
server.close();
cleanup();
});
Intl Config:
export const setUpIntlConfig = () => {
if (global.Intl) {
Intl.NumberFormat = IntlPolyfill.NumberFormat;
Intl.DateTimeFormat = IntlPolyfill.DateTimeFormat;
} else {
global.Intl = IntlPolyfill;
}
};
export const RenderWithReactIntl = (component: any) => {
return {
...render(
<IntlProvider locale="en" messages={en}>
{component}
</IntlProvider>
)
};
};
I'm using msw as mock server. Please guide us, if we are missing any configs.
Test cases:
test('fire get resource details with data', async () => {
jest.spyOn(SGWidgets, 'getAuthorizationHeader').mockReturnValue('test-access-token');
process.env = Object.assign(process.env, { REACT_APP_DIAM_API_ENDPOINT: '' });
RenderWithReactIntl(<AllocatedAccess diamUserId={diamUserIdWithData} />);
await waitForElementToBeRemoved(() => screen.getByText(/loading data.../i));
const viewResource = screen.getAllByText(/view resource/i);
fireEvent.click(viewResource[0]);
await waitForElementToBeRemoved(() => screen.getByText(/loading/i));
const ownerName = screen.getByText(/benedicte masson/i);
expect(ownerName).toBeInTheDocument();
});
test('fire get resource details with data----2', async () => {
jest.spyOn(SGWidgets, 'getAuthorizationHeader').mockReturnValue('test-access-token');
process.env = Object.assign(process.env, { REACT_APP_DIAM_API_ENDPOINT: '' });
RenderWithReactIntl(<AllocatedAccess diamUserId={diamUserIdWithData} />);
await waitForElementToBeRemoved(() => screen.getByText(/loading data.../i));
const viewResource = screen.getAllByText(/view resource/i);
fireEvent.click(viewResource[0]);
await waitForElementToBeRemoved(() => screen.getByText(/loading/i));
const ownerName = screen.getByText(/benedicte masson/i);
expect(ownerName).toBeInTheDocument();
});
Can you try these changes:
test('fire get resource details with data----2', async () => {
jest.spyOn(SGWidgets, 'getAuthorizationHeader').mockReturnValue('test-access-token');
process.env = Object.assign(process.env, { REACT_APP_DIAM_API_ENDPOINT: '' });
RenderWithReactIntl(<AllocatedAccess diamUserId={diamUserIdWithData} />);
await waitFor(() => expect(screen.getByText(/loading data.../i)).not.toBeInTheDocument());
const viewResource = screen.getAllByText(/view resource/i);
act(() => {
fireEvent.click(viewResource[0]);
});
await waitFor(() => expect(screen.getByText(/loading/i)).not.toBeInTheDocument());
expect(screen.getByText(/benedicte masson/i)).toBeVisible();
});
I've got into the habit of using act() when altering something that's visible on the screen. A good guide to here: https://testing-library.com/docs/guide-disappearance/
Using getBy* in the waitFor() blocks as above though, you may be better off specifically checking the text's non-existence.
Without seeing your code it's difficult to go any further. I always say keep tests short and simple, we're testing one thing. The more complex they get the more changes for unforeseen errors. It looks like you're rendering, awaiting a modal closure, then a click, then another modal closure, then more text on the screen. I'd split it into two or more tests.

Test a component with useState and setTimeout

Code structure is as same as given below:
FunctionComponent.js
...
const [open, handler] = useState(false);
setTimeout(() => {handler(true);}, 2000);
...
return (
...
<div className={active ? 'open' : 'close'}>
)
comp.test.js
jest.useFakeTimers();
test('test case 1', () => {
expect(wrapper.find('open').length).toBe(0);
jest.advanceTimersByTime(2000);
expect(wrapper.find('open').length).toBe(1);
jest.useRealTimers();
});
The problem is that the expression written in bold in test is saying the length of open class is still 0, so actual and expected are not meeting.
You want to test the outcome of the hook and not the hook itself since that would be like testing React. You effectively want a test where you check for if the open class exists and then doesn't exist (or vice versa), which it looks like you're trying.
In short, to solve your issue you need to use ".open" when selecting the class. I would also suggest using the .exists() check on the class instead of ".length()" and then you can use ".toBeTruthy()" as well.
You could look into improve writing your tests in a Jest/Enzyme combined format as well:
import { shallow } from 'enzyme';
import { FunctionComponent } from './FunctionComponent.jsx';
jest.useFakeTimers();
describe('<FunctionCompnent />', () => {
const mockProps = { prop1: mockProp1, prop2: mockProp2, funcProp3: jest.fn() };
const wrapper = shallow(<FunctionComponent {...mockProps} />);
afterEach(() => {
jest.advanceTimersByTime(2000);
});
afterAll(() => {
jest.useRealTimers();
});
it('should render as closed initially', () => {
expect(wrapper.find('.close').exists()).toBeTruthy();
// you could also add the check for falsy of open if you wanted
// expect(wrapper.find('.open').exists()).toBeFalsy();
});
it('should change to open after 2 seconds (or more)', () => {
expect(wrapper.find('.open').exists()).toBeTruthy();
// you could also add the check for falsy of close if you wanted
// expect(wrapper.find('.close').exists()).toBeFalsy();
});
});
EDIT: Sorry realised I wrote the test backwards after checking your code again, they should be fixed now.

How to spy on a default exported function with Jest?

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');

How to test in Jest the MediaQuerList.addListener

I would like to test that my function has been triggered properly (with the right parameters) but I can't find a way to make it...
I have a custom addEventListener take the name of the media query, the media query itself and a dispatch function
// ... Inside my class
addEventListener(name, mediaQuery, dispatch) {
// Initialize the mediaQueryList and store it in our list
const mediaQueryList = window.matchMedia(mediaQuery);
mediaQueryList.addListener(
mediaQueryListEvent => this.onScreenChange(name, mediaQueryListEvent)
);
this.mediaQueries.set(name, {
mediaQueryList,
dispatch
});
// Then we look even for the first time on which breakPoint we are
this.searchAndDispatchBreakpoint(name, mediaQueryList);
}
Any idea how I can test that my onScreenChange has been properly called with the right arguments?
Ok so after hours and hours of search and tries, I figured out a way.
I succeed in mocking the window.matchMedia and the addListener inside
Once done, I just have to test the arguments sent to my method.
Here is my jest test
it('should test the media query addListener method', () => {
const bpManager = new BreakpointManager();
const mediaQuery = 'max-width: 1080px';
const name = 'name';
const mediaQueryListEvent = 'mediaQueryListEvent';
bpManager.onScreenChange = jest.fn();
window.matchMedia = jest.fn().mockImplementation(query => ({
matches: false,
media: query,
onchange: null,
addListener: e => e(mediaQueryListEvent),
removeListener: jest.fn(),
}));
bpManager.addEventListener(name, mediaQuery, dispatch);
expect(window.matchMedia).toHaveBeenCalledWith(mediaQuery);
expect(bpManager.onScreenChange).toBeCalledWith(name, mediaQueryListEvent);
});
And my method addEventListener
addEventListener(name, mediaQuery, dispatch) {
// Initialize the mediaQueryList and store it in our list
const mediaQueryList = window.matchMedia(mediaQuery);
mediaQueryList.addListener(
mediaQueryListEvent => this.onScreenChange(name, mediaQueryListEvent)
);
this.mediaQueries.set(name, {
mediaQueryList,
dispatch
});
// Then we look even for the first time on which breakPoint we are
this.searchAndDispatchBreakpoint(name, mediaQueryList);
}

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