dependency on a single test suit causes other test suits to fail - Jest - reactjs

I am mocking the following library on one of my test suits:
it's react-native-awesome-card.io:
jest.mock('react-native-awesome-card-io', () => {
return {
CardIOUtilities: {
preload: jest.fn(() => Promise.resolve('the response')),
},
}
})
describe('PaymentDetails', () => {
test('snapshot = PaymentDetails not empty', () => {
const options = {
navigation: {
state: {
params: {
paymentMethod: {
accountName: 'Test Account',
expMonth: 11,
expYear: 2021,
number: '4111111111111111',
type: 'Visa',
preferred: true,
},
},
},
},
getUserInfo: { locale: 'en-US' },
}
snapshot(shallow(<PaymentDetails {...options} />))
})
This seems to work fine for this test but I notice my other test suits start failing with errors like these:
It seems they all need that library mocked on them as well. But why?
And what is the solution to this? I hate replication mock code on each one of them.

Related

Axios Spy not being called correct number of times in Jest

I have a React context I am testing that runs a single function to check for an application update. The checkForUpdate function looks like this:
async function checkForUpdate() {
if (isPlatform('capacitor')) {
const maintanenceURL =
'https://example.com/maintenance.json';
const updateURL =
'https://example.com/update.json';
try {
const maintanenceFetch: AxiosResponse<MaintanenceDataInterface> =
await axios.get(maintanenceURL);
console.log('maintain', maintanenceFetch);
if (maintanenceFetch.data.enabled) {
setUpdateMessage(maintanenceFetch.data.msg);
return;
}
const updateFetch: AxiosResponse<UpdateDataInterface> = await axios.get(
updateURL
);
console.log('updateFetch', updateFetch);
if (updateFetch.data.enabled) {
const capApp = await App.getInfo();
const capAppVersion = capApp.version;
console.log('Thi is a thinkg', capAppVersion);
if (isPlatform('android')) {
console.log('hi');
const { currentAndroid, majorMsg, minorMsg } = updateFetch.data;
const idealVersionArr = currentAndroid.split('.');
const actualVersionArr = capAppVersion.split('.');
if (idealVersionArr[0] !== actualVersionArr[0]) {
setUpdateMessage(majorMsg);
setUpdateAvailable(true);
return;
}
if (idealVersionArr[1] !== actualVersionArr[1]) {
setUpdateMessage(minorMsg);
setUpdateAvailable(true);
return;
}
} else {
const { currentIos, majorMsg, minorMsg } = updateFetch.data;
const idealVersionArr = currentIos.split('.');
const actualVersionArr = capAppVersion.split('.');
if (idealVersionArr[0] !== actualVersionArr[0]) {
setUpdateMessage(majorMsg);
setUpdateAvailable(true);
return;
}
if (idealVersionArr[1] !== actualVersionArr[1]) {
setUpdateMessage(minorMsg);
setUpdateAvailable(true);
return;
}
}
}
} catch (err) {
console.log('Error in checkForUpdate', err);
}
}
}
For some reason, in my test I wrote to test this, my axiosSpy only shows that it has been called 1 time instead of the expected 2 times. The console logs I posted for both get requests run as well. I cannot figure out what I am doing wrong.
Here is the test:
it.only('should render the update page if the fetch call to update bucket is enabled and returns a different major version', async () => {
const isPlatformSpy = jest.spyOn(ionicReact, 'isPlatform');
isPlatformSpy.mockReturnValueOnce(true).mockReturnValueOnce(true);
const appSpy = jest.spyOn(App, 'getInfo');
appSpy.mockResolvedValueOnce({
version: '0.8.0',
name: 'test',
build: '123',
id: 'r132-132',
});
const axiosSpy = jest.spyOn(axios, 'get');
axiosSpy
.mockResolvedValueOnce({
data: {
enabled: false,
msg: {
title: 'App maintenance',
msg: 'We are currently solving an issue where users cannot open the app. This should be solved by end of day 12/31/2022! Thank you for your patience 😁',
btn: 'Ok',
type: 'maintenance',
},
},
})
.mockResolvedValueOnce({
data: {
current: '1.0.0',
currentAndroid: '1.0.0',
currentIos: '2.0.0',
enabled: true,
majorMsg: {
title: 'Important App update',
msg: 'Please update your app to the latest version to continue using it. If you are on iPhone, go to the app store and search MO Gas Tax Back to update your app. The button below does not work but will in the current update!',
btn: 'Download',
type: 'major',
},
minorMsg: {
title: 'App update available',
msg: "There's a new version available, would you like to get it now?",
btn: 'Download',
type: 'minor',
},
},
});
customRender(<UpdateChild />);
expect(axiosSpy).toHaveBeenCalledTimes(2);
});

Mocking window object with Jest

I have a function which mocks a hostname. It is defined before the actual test, so before describe.
const mockHost = (hostname: string) => {
global.window = Object.create(window);
Object.defineProperty(window, 'location', {
value: {
hostname
}
});
}
I run two tests with a different hostname
it('should show local hostname', async() => {
fetchMock.getOnce('/test', {
data: {
green: true,
},
});
mockHost('local')
const instance = render(<Component />);
expect(instance.getByText('local')).toBeVisible();
});
it('should show different hostname', async() => {
fetchMock.getOnce('/test', {
data: {
green: true,
},
});
mockHost('different')
const instance = render(<Component />);
expect(instance.getByText('different')).toBeVisible();
});
It's a fake example and when run separately both tests work. But if both are run in a bunch a hostname won't reset and stays as local, causing second test with a different hostname to fail. I am new to testing. What am I doing wrong?
I found this article on Medium 'Mocking window object with Jest' from Marek Rozmus on Oct 14, 2021, which describes, that I was doing the static mocking, which is only suitable if we need to test only one value. Im my case value changes, so I need dynamic hostname mocking.
So instead mockHost(), I'd need the following:
const mockPathname = jest.fn();
Object.defineProperty(window, 'location', {
value: {
get hostname() {
return mockPathname();
},
},
});
In tests itself I'd call:
mockPathname.mockReturnValue('some hostname');
And I'd need to clear all Mocks after each test
afterEach(() => {
jest.resetAllMocks();
});

Find element in an array of object with key array types

I would like to extract all active modules and actions with given roles.
getAvailableModulesAndActionsWithRoles(roles: string[]): IHubModule[] {
const modules = [...this.getModules()];
const activeModules = modules.filter(module => module.isEnabled);
activeModules.map(module => {
Object.keys(module.actions).map(action => {
roles.map(role => {
if (!module.actions[action]?.includes(role)) {
delete module.actions[action];
return;
}
});
});
});
return activeModules;
}
Here is how my module interface looks like
export interface IHubModule {
name: ModuleName;
isEnabled: boolean;
actions: Record<Topic, string[]>;
}
I passed an array of roles as an argument and I would like to extract all active actions for a given roles.
Here is how my test looks like
it('returns only active modules and actions for a list of roles', () => {
const moduleInfos: IHubModule[] = [
{
name: ModuleNameTest.TEST as any,
isEnabled: true,
actions: {
get: [BaseRole.STUDENT, BaseRole.EMPLOYEE],
post: [BaseRole.ADMIN, BaseRole.UNVERIFIED_GUEST],
update: [],
del: [BaseRole.ADMIN, BaseRole.UNVERIFIED_GUEST, BaseRole.STUDENT],
},
},
];
jest
.spyOn(hubService, 'getModules')
.mockImplementation(() => moduleInfos);
const results: IHubModule[] = [
{
name: ModuleNameTest.TEST as any,
isEnabled: true,
actions: {
post: [BaseRole.ADMIN, BaseRole.UNVERIFIED_GUEST],
del: [BaseRole.ADMIN, BaseRole.UNVERIFIED_GUEST, BaseRole.STUDENT],
},
},
{
name: ModuleNameTest.TEST as any,
isEnabled: true,
actions: {
get: [BaseRole.STUDENT, BaseRole.EMPLOYEE],
del: [BaseRole.ADMIN, BaseRole.UNVERIFIED_GUEST, BaseRole.STUDENT],
},
},
]
expect(
hubService.getAvailableModulesAndActionsWithRoles([
BaseRole.ADMIN,
BaseRole.STUDENT,
]),
).toStrictEqual(results);
});
This only work for one argument and I would like to get all module with available action for a given role. Is there a way to skip the action key instead of deleting it if the role is not found ? Because I needed on the next iteration.
I think that instead of modifying the original modules, you should create copies with the required content.
getAvailableModulesAndActionsWithRoles(roles: string[]): IHubModule[] {
const activeModules = this.getModules().filter(module => module.isEnabled);
// selecting modules corresponding to each action
return roles.flatMap(role => activeModules.map(
module => {
const actions = {};
Object.keys(module.actions)
.filter(key => Array.isArray(module.actions[key]) && module.actions[key].includes(role))
.forEach(key => {
actions[key] = module.actions[key];
});
return {...module, actions } as IHubModule;
}
));
//TODO We should also filter modules having empty actions
}

cloudconvert API NOT working with netlify Serverless function

I was using cloudconvert api from node JS, which was working fine when I hosted in heroku. But when I made netlify serverless function it is returning error. Is it because serverless function exits before completing the task?
try {
let job = await cloudConvert.jobs.create({
tasks: {
'const-1': {
operation: 'const/raw',
// file: file_string_output,
file: '<div>Welcome ... testing...</div>',
filename: `${fileName}.html`,
},
'task-1': {
operation: 'convert',
input_format: 'html',
output_format: 'pdf',
engine: 'chrome',
input: ['const-1'],
zoom: 1,
print_background: true,
display_header_footer: false,
wait_until: 'load',
wait_time: 0,
},
'export-1': {
operation: 'export/s3',
input: ['task-1'],
bucket: process.env.S3_BUCKET_NAME,
region: process.env.S3_BUCKET_REGION,
access_key_id: process.env.S3_ACCESS_KEY,
secret_access_key: process.env.S3_ACCESS_SECRETE,
key: `${process.env.S3_BUCKET_FOLDER}/${fileName}.pdf`,
},
},
})
cloudConvert.jobs.subscribeEvent(job.id, 'finished', (event) => {
console.log('cloud convert stages finished', event.job)
})
cloudConvert.jobs.subscribeEvent(job.id, 'error', (event) => {
console.log('error', event.job)
})
cloudConvert.jobs.subscribeTaskEvent(job.id, 'finished', async (event) => {
console.log('cloud convert Task stages finished', event.job)
})
cloudConvert.jobs.subscribeTaskEvent(job.id, 'error', (event) => {
console.log('Task on error', event.task)
})
} catch (error) {
console.log(' Cloud convert key is invalid??:', error)
}
I have figured out the problem. In below code, there was a typo.
operation: 'const/raw', ==>>>> operation: 'import/raw'
It was my bad. Since the netlify serverless did not support Es6, I have to change all import syntax to require() syntax and I had a global search and replace import==> const which effected here as well. So silly me...
'const-1': {
operation: 'const/raw', ==>>>>
// file: file_string_output,
file: '<div>Welcome ... testing...</div>',
filename: `${fileName}.html`,
},

How to convert react asynchronous tests synchronously?

Below is the test for ag-grid. Documentaion can be found at https://www.ag-grid.com/javascript-grid-testing-react/
Few of my tests are failing in CI when tests are asynchronous as test1. Is there any solution to make it consistent? I tried test2 approach make it synchronous but that is also failing. Is there any better way to run tests with consistency?
describe('ag grid test 1', () => {
let agGridReact;
let component;
const defaultProps = {
//.....
}
beforeEach((done) => {
component = mount(<CustomAGGridComp {...defaultProps} />);
agGridReact = component.find(AgGridReact).instance();
// don't start our tests until the grid is ready
ensureGridApiHasBeenSet(component).then(() => done(), () => fail("Grid API not set within expected time limits"));
});
it('stateful component returns a valid component instance', async () => {
expect(agGridReact.api).toBeTruthy();
//..use the Grid API...
var event1 = {
type: 'cellClicked', rowIndex: 0, column: { field: "isgfg", colId: "isgfg", headerName: "Property 2" },
event: {
ctrlKey: false,
shiftKey: false
}
}
await agGridReact.api.dispatchEvent(event1)
//some expect statements
})
});
describe('ag grid test 2', () => {
let agGridReact;
let component;
const defaultProps = {
//.....
}
beforeEach((done) => {
component = mount(<CustomAGGridComp {...defaultProps} />);
agGridReact = component.find(AgGridReact).instance();
// don't start our tests until the grid is ready
ensureGridApiHasBeenSet(component).then(() => done(), () => fail("Grid API not set within expected time limits"));
});
it('stateful component returns a valid component instance', () => {
expect(agGridReact.api).toBeTruthy();
//..use the Grid API...
var event1 = {
type: 'cellClicked', rowIndex: 0, column: { field: "isgfg", colId: "isgfg", headerName: "Property 2" },
event: {
ctrlKey: false,
shiftKey: false
}
}
agGridReact.api.dispatchEvent(event1);
setTimeout(() => {
//some expect statements
}, 500);
})
});

Resources