Jest mock function inside function - javascript

I have no idea how to mock return value of inner function inside jest
I tried different approaches. Finally I found this answer
but value not mocking for some reason, here it is example:
countries.js
export const countryList = () => [
{
label: '+244',
value: 'Angola',
}, // list of all possible countries very long...
];
export const getSortedCountryData = intlLang =>
countriesList()
.sort((compare, comparable) =>
compare.value.localeCompare(comparable.value, intlLang, { sensitivity: 'base' }));
countries.test.js
import * as countyListHelper from './countries';
describe('countries list', () => {
test('returns list of countries', () => {
const mockFn = jest.mock();
const expectedList = [
{
label: '+244',
value: 'Angola',
},
{
label: '+43',
value: 'Austria',
},
];
mockFn.spyOn(countyListHelper, 'countriesList').mockReturnValue(expectedList);
// console.log('if return value mocked correctly',
// countyListHelper.countriesList() === expectedList); // true
expect(countyListHelper.getSortedCountryData('en')).toEqual(expectedList);
// shows error with received value list of all countries instead of mocked one
});
});
Please suggest me any possible solutions why inside test function mocked return value for inner function is ignored. Setup from Create React App.

The question you linked to has a currently accepted answer that doesn't work. I added a new answer with an explanation and working example.
The same concept applies here: a mock replaces the module export of a function so to be able to mock countriesList within getSortedCountryData you have to call the module export for countriesList.
One option is to move countriesList to its own module.
The other option is to take advantage of the fact that "ES6 modules support cyclic dependencies automatically" so it is perfectly valid to import the module into itself so that you can call the module export for countriesList:
countries.js
import * as countyListHelper from './countries';
export const countriesList = () => [
{
label: '+244',
value: 'Angola',
}, // list of all possible countries very long...
];
export const getSortedCountryData = intlLang =>
countyListHelper.countriesList()
.sort((compare, comparable) =>
compare.value.localeCompare(comparable.value, intlLang, { sensitivity: 'base' }));
countries.test.js
import * as countyListHelper from './countries';
describe('countries list', () => {
test('returns list of countries', () => {
const expectedList = [
{
label: '+244',
value: 'Angola',
},
{
label: '+43',
value: 'Austria',
},
];
const spy = jest.spyOn(countyListHelper, 'countriesList');
spy.mockReturnValue(expectedList);
expect(countyListHelper.getSortedCountryData('en')).toEqual(expectedList); // Success!
spy.mockRestore();
});
});

Related

How to use jest.spyOn for a function (React)

How do I write a test for this function? I first tried to include an optional param in the function such as mockData and go from there, but that seems too tedious. I have a function, something like this:
export async function getBreadcrumbs(service, id) {
const { folderId, label } = await getFileDetailsById(id);
const { breadcrumbs } = await getFoldersService(service, folderId);
return [...breadcrumbs, { label, id: '' }];
}
I'm thinking jest.spyOn is what I need to write the test with but I'm not sure how to approach it. I started with:
import * as crumbs from '.....data'
describe('getCrumbs', () => {
it('should return breadcrumbs name and id', async () => {
const data = jest.spyOn(crumbs, 'getBreadcrumbs')
});
});
But I'm not sure where to go from here. How can I mock a return of this function? This function returns:
[{ displayName: 'Hey', id: '' }]
getFileDetailsById and getFoldersService are API calls.
TLDR: I have a function and I'm unsure how to mock its return.

Error: Maximum update depth exceeded. Component calls setState inside useEffect,..., or one of the dependencies changes on every render

I have error like in title. But I don't know how my dependencie change every render because it is in useEffect. I suspected it was related to the map function so I moved it higher in the hierarchy to "injectEndpoints"->"transformResponse" but that didn't help. I don't know how to change it or maybe that's not the point?
export const parametersApi = myApi
.injectEndpoints({
endpoints: (builder) => ({
getParameters: builder.query<
ExtendedParameter[],
string
>({
query: (id) =>
`/${id}/parameters`,
providesTags: (_result, _error, id) => [
{
type: `Parameters`,
id: `PLIST_${id}`,
},
],
transformResponse: (response: parameter[]) => {
return response.map((param) => {
return { id: param.name, ...param };
});
},
}),
....
export const {
useGetParametersQuery } = parametersApi;
The file in which the error occurs:
const selectedParameters =
useGetParametersQuery(master.id, {
skip: !master,
}).data || [];
...
useEffect(() => {
console.log("selectedParameters", selectedParameters);
setParameters(selectedParameters);
}, [selectedParameters]);
console.log("selectedParameters", selectedParameters);
|| []
creates a new array every render while there is no .data yet - so selectedParameters will be a new reference and trigger an useEffect every time.
Do something like const EMPTY_ARR = [] outside your component and do || EMPTY_ARR to keep a stable reference here.

Jest Mock returns undefined instead of data

I'm trying to mock a function and not sure what i'm doing wrong here. I have this function "getGroups"
getGroups:
export const getGroups = async () => {
try {
const groupApiUrl = getDashboardPath(GROUPS_TAB_INDEX);
const data = await fetch(groupApiUrl, { cache: 'force-cache' });
const userData = await data.json();
return userData;
} catch (error) {
throw Error(error);
}
};
___mocks___/getGroups.js:
export default async () => {
return {
groups: [
{ id: 1, name: 'Data1' },
{ id: 2, name: 'Data2' }
]
};
};
getGroups.test.js:
jest.mock('./getGroups.js');
// eslint-disable-next-line import/first
import { getGroups } from './getGroups';
const fakeRespose = {
groups: [
{ id: 1, name: 'Data1' },
{ id: 2, name: 'Data2' }
]
};
describe('getGroups', () => {
it('returns data', async () => {
const data = await getGroups();
console.log('DATA', data); <---- UNDEFINED?
expect(data).toBeDefined();
expect(data).toMatchObject(fakeRespose);
});
it('handles error', async () => {
// const data = await getGroups();
await getGroups().toThrow('Failed');
});
});
What are you doing wrong here?
Default export in your mock instead of named as in the implementation
In your implementation you're using named export and you're importing { getGroups } so to make it work you need to change your mock like this
__mocks__\getGroups.js
export const getGroups = async () => {
return {
groups: [
{ id: 1, name: 'Data1' },
{ id: 2, name: 'Data2' }
]
};
};
working example
TL;DR
Testing mock
There is no point at all to test mock function. This does not proves your implementation is working. Even if you change your implementation your tests will still pass.
Use mocks only for the dependencies of your implementation
Use jest.genMockFromModule
It will create jest.fn() for each of the module's exported methods and will preserve the constants, allowing you to change the return value/implementation for some test cases and will also be able to write assertions if the function have been called
__mocks__\getGroups.js
const mock = jest.genMockFromModule('../getGroups');
mock.getGroups.mockResolvedValue({
groups: [
{ id: 1, name: 'Data1' },
{ id: 2, name: 'Data2' }
]
})
module.exports = mock;
Jest will automatically hoist jest.mock calls (read more...)
So you can safely leave the import statements first and then call jest.mock
From Jest Docs, here's an example of a Mock.
jest.mock('../moduleName', () => {
return jest.fn(() => 42);
});
// This runs the function specified as second argument to `jest.mock`.
const moduleName = require('../moduleName');
moduleName(); // Will return '42';
In your case data is undefined, because you haven't actually supplied a mocked implementation for the function or the mock hasn't worked and you're still calling the original function.
Example Reference: https://jestjs.io/docs/en/jest-object#jestmockmodulename-factory-options
However, in your simple case you could also solve this with a spy, either jest.spyOn or jest.fn(). Here are two solutions to what you're trying to achieve. You can look at the code and run it here: https://repl.it/repls/FairUnsungMice
UPDATE after comment:
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. Note that the
__mocks__ folder is case-sensitive, so naming the directory __MOCKS__ will break on some systems.
Double check the naming, directory structure & type of exports you've setup - they should match. Also, it's worth checking this out: https://github.com/facebook/jest/issues/6127 - looks like an open issue with jest. If you need a solution, look at using a different approach as I mentioned.
Reference: https://jestjs.io/docs/en/manual-mocks

How to call defaultExportCsv() of ReactJS material-table from the overriden export function

I'm a beginner in reactjs. I'm handling material-table (material-table.com) which should be capable of download data with the custom filename with the current timestamp. This can be solved by assigning the desired filename to the property options.exportFileName in <MaterialTable>. However, this timestamp will contain time when the page rendered, but I need the timestamp when the export button was clicked.
I was thinking if I can achieve this by calling defaultExportCsv from the overridden export function after setting the file name like below:
function TableExport({model}) {
const handleExportCsv = () => {
options.exportFileName = 'filename_' + moment().format('YYYY-MM-DDTHHmmss');
// calling defaultExportCsv here
}
return ( <
MaterialTable columns = {
[{
title: 'ID',
field: 'id'
},
{
title: 'Name',
field: 'name'
},
]
}
data = {
model
}
title = "Title"
options = {
{
columnsButton: true,
exportButton: true,
actionsColumnIndex: -1,
exportCsv: handleExportCsv,
}
}
/>
);
}
I'm unable to call defaultExportCsv() from the handleExportCsv(), can somebody help me in this regard ?
You can write your own exportCsv callback fn but then you are responsible for generating CSV data for exporting also.
Maybe you can find the following useful. For data exporting you can use the same library as in component MaterialTable. First, don't forget to install the library:
npm install --save filefy
And import library
const _filefy = require("filefy");
Inside functional component define fn for exporting data into CSV format:
const exportCsv = (allColumns, allData) => {
const columns = allColumns.filter(columnDef => columnDef["export"] !== false);
const exportedData = allData.map(rowData => columns.map(columnDef => rowData[columnDef.field]));
new _filefy.CsvBuilder('filename_' + moment().format('YYYY-MM-DDTHHmmss'))
.setDelimeter(';')
.setColumns(columns.map(columnDef => columnDef.title))
.addRows(exportedData)
.exportFile();
}
Inside MaterialTable connect exportCsv
<MaterialTable
...
options={
{
...
exportButton: true,
exportCsv,
}
}

Unit test Vuex getters that depend on other getters

I've manage to test Vuex getters that are isolated from other code. I'm now facing some issues when a getter depends on other getters, see the following example:
getters.js
export const getters = {
getFoo(state) => prefix {
return `${prefix}: ${state.name}`;
},
getFancyNames(state, getters) {
return [
getters.getFoo('foo'),
getters.getFoo('bar')
]
}
}
getters.spec.js
import { getters } = './getters';
const state = {
name: 'stackoverflow'
};
describe('getFoo', () => {
it('return name with prefix', () => {
expect(getters.getFoo(state)('name')).toBe('name: stackoverflow');
});
});
describe('getFancyNames', () => {
// mock getters
const _getters = {
getFoo: getters.getFoo(state)
}
it('returns a collection of fancy names', () => {
expect(getters.getFancyNames(state, _getters)).toEqual([
'foo: stackoverflow',
'bar: stackoverflow'
]);
});
});
When the tested getter depends on other getter that has arguments this means that I've reference the original getter.getFoo on the mock, and this breaks the idea of mocking, since the tests start to have relation with each other. When the getters grow, and the dependency graph has several levels it makes the tests complex.
Maybe this is the way to go, just wanted to check that I'm not missing anything...
I agree with you that referencing the actual collaborator in your mock defeats the purpose of a mock. So instead I would simply directly return whatever you want your collaborator to return.
In your example, instead of doing something like this:
// mock getters
const _getters = {
getFoo: getters.getFoo(state)
}
You would simply put in whatever getters.getFoo(state) would return:
const _getters = {
getFoo: 'foobar'
}
If you have a getter that takes an additional argument you would simply return a function that returns a constant:
const _getters = {
getFoo: x => 'foobar',
}
Since I'm using Jest there is an option in the jest mock function that let's specify the return value when called:
mockReturnValueOnce or mockReturnValue
More information can be found here: https://facebook.github.io/jest/docs/en/mock-functions.html#mock-return-values
Using the same code as in the question this could be solved like this:
const state = {
name: 'stackoverflow'
}
describe('getFancyNames', () => {
const getFoo = jest.fn()
getFoo.mockReturnValueOnce('foo: stackoverflow')
getFoo.mockReturnValueOnce('bar: stackoverflow')
it('returns a collection of fancy names', () => {
expect(getters.getFancyNames(state, { getFoo })).toEqual([
'foo: stackoverflow',
'bar: stackoverflow'
])
})
})
A cleaner way that I have found is to create your own mocked getters object. This only works if the getter uses the unaltered state like the question does.
const state = {
name: 'stackoverflow'
}
describe('getFancyNames', () => {
const mockedGetters = {
...getters, // This can be skipped
getFoo: getters.getFoo(state), // We only overwrite what is needed
};
it('returns a collection of fancy names', () => {
expect(getters.getFancyNames(state, mockedGetters)).toEqual([
'foo: stackoverflow',
'bar: stackoverflow'
])
})
})
Extra
In the case that you do need to call other getter functions just pass the mocked getters objects into another mocked getters object. It sounds worse than is actually is.
getters.py
export const getters = {
getBar(state) = { // new extra hard part!
return state.bar,
},
getFoo(state, getters) => prefix {
return `${prefix}: ${state.name} with some ${getters.getBar}`;
},
getFancyNames(state, getters) {
return [
getters.getFoo('foo'),
getters.getFoo('bar')
]
}
}
const _mockedGetters = {
...getters, // This can be skipped
getFoo: getters.getFoo(state), // We only overwrite what is needed
};
const mockedGetters = {
.._mockedGetters, // Use the mocked object!
getBar: getters.getBar(state, _mockedGetters), // We only overwrite what is needed
};
// continue down the line as needed!

Categories