I’m trying to mock a fetch() that retrieves data into a component.
I’m using this as a model for mocking my fetches, but I’m having trouble getting it to work.
I’m getting this error when I run my tests: babel-plugin-jest-hoist: The module factory of 'jest.mock()' is not allowed to reference any out-of-scope variables.
Is there a way I can have these functions return mock data instead of actually trying to make real API calls?
Code
utils/getUsers.js
Returns users with roles mapped into each user.
const getUsersWithRoles = rolesList =>
fetch(`/users`, {
credentials: "include"
}).then(response =>
response.json().then(d => {
const newUsersWithRoles = d.result.map(user => ({
...user,
roles: rolesList.filter(role => user.roles.indexOf(role.iden) !== -1)
}));
return newUsersWithRoles;
})
);
component/UserTable.js
const UserTable = () => {
const [users, setUsers] = useState([]);
useEffect(() => {
getTableData();
}, []);
const getTableData = () => {
new Promise((res, rej) => res(getRoles()))
.then(roles => getUsersWithRoles(roles))
.then(users => {
setUsers(users);
});
};
return (...)
};
component/tests/UserTable.test.js
import "jest-dom/extend-expect";
import React from "react";
import { render } from "react-testing-library";
import UserTable from "../UserTable";
import { getRoles as mockGetRoles } from "../utils/roleUtils";
import { getUsersWithRoles as mockGetUsersWithRoles } from "../utils/userUtils";
const users = [
{
name: "Benglish",
iden: "63fea823365f1c81fad234abdf5a1f43",
roles: ["eaac4d45c3c41f449cf7c94622afacbc"]
}
];
const roles = [
{
iden: "b70e1fa11ae089b74731a628f2a9b126",
name: "senior dev"
},
{
iden: "eaac4d45c3c41f449cf7c94622afacbc",
name: "dev"
}
];
const usersWithRoles = [
{
name: "Benglish",
iden: "63fea823365f1c81fad234abdf5a1f43",
roles: [
{
iden: "eaac4d45c3c41f449cf7c94622afacbc",
name: "dev"
}
]
}
];
jest.mock("../utils/userUtils", () => ({
getUsers: jest.fn(() => Promise.resolve(users))
}));
jest.mock("../utils/roleUtils", () => ({
getRolesWithUsers: jest.fn(() => Promise.resolve(usersWithRoles)),
getRoles: jest.fn(() => Promise.resolve(roles))
}));
test("<UserTable/> show users", () => {
const { queryByText } = render(<UserTable />);
expect(queryByText("Billy")).toBeTruthy();
});
By default
jest.mock
calls are hoisted bybabel-jest
……this means they run before anything else in your test file, so any variables declared in the test file won’t be in scope yet.
That is why the module factory passed to
jest.mock
can’t reference anything outside itself.One option is to move the data inside the module factory like this:
Another option is to mock the functions using
jest.spyOn
:And another option is to auto-mock the modules:
…and add the mocked response to the empty mock functions.
This is a generalized answer. First, do
jest.mock(../service)
, inside your test.Then go to your service file where you make the calls. Create a
__mocks__
file and inside make a file named the same as your service file.Then make functions for each service you want that resolves a promise in order to be asynchronous like regular calls.
const getUser = () => promise.resolve()
The above is a general example. If you want data, make a mockData object/array of your choice and serve it, with the function.
You need to rename the variables used in the scope of the mocks to be prefixed with
mock
(e.g.mockUsers
).Jest does some hoisting magic to allow you to replace the imported modules with mocks, but does seem to require these special variable name prefixes to do its thing.
Don’t mock the tool making API calls; stub the server responses. Here’s how I would re-write your test using an HTTP interceptor called nock.
Now your test suite is unaware of how you get the data, and you don’t have to maintain complicated mocks. Check out Testing Components that make API calls for a deeper dive.