I recently had a troublesome React component.
It was a component that was using the browser’s
fetch
API to load
and insert SVG icons the page.
In and of itself that’s fine.
Browsers have all the required components to use fetch
.
However,
Node.js is not a browser
and it doesn’t have fetch
.
So what can we do?
One thing we could do is use some fetch-like polyfill.
There’s
node-fetch,
fetch-mock,
jest-fetch-mock,
cross-fetch,
and many others that might help us do that.
However,
this not only involves modifying the global object to add fetch
,
but also mocking every call to fetch
so it returns the icons that we want.
Since most of the time I’m not testing the icons of a component,
it would be inappropriate to mock this for unit tests.
The way I solved this was to use Jest mocks
and mocked out the whole icon component,
so that it never calls fetch
.
We stopped making fetch happen,
but the solution wasn’t intuitive.
Mock ES Modules with Jest
First,
to mock a component, you use
jest.mock("path/to/RealComponent")
.
You can specify an mock implementation inline like
jest.mock("../src/Icon" () => { ... })
.
It needs to return a module, which is an object with keys as the exports. So if your component normally exports like so:
export { A, B };
Then your Jest mock can supply both of those exported things:
jest.mock("../src/Icon", () => {
return {
A: true,
B: () => {
return <></>;
},
};
});
Mock ES Modules with default and named exports
What about default
exports,
or both named and default
exports?
To mock a module with both,
you can use the default
key,
but you also must specify that it’s an ES6 module with a special
__esModule
key:
export { A };
export default B;
jest.mock("../src/Icon", () => {
return {
__esModule: true,
A: true,
default: () => {
return <div></div>;
},
};
});
You can then put this in your test, but it must be outside of any scope to work. It can’t be in a function, a before block, or anything else. This is a pain to abstract and use in other (all) tests. So I used a mock file.
Mock ES Modules with Jest file mocks
These mocks need to be in a __mocks__
folder next to the component,
and also have the same file name.
For example,
your folder structure might look like:
src
├── Icon.tsx
├── __mocks__
│ └── Icon.tsx
and then you can reimplement the component:
// src/__mocks__/Icon.tsx
import React from "react";
import { ReactElement } from "react";
const A = "abcdefg";
const B = (): ReactElement => {
return <></>;
};
export { A };
export default B;
Finally, in your test, you can mock the real component, and Jest will magically find the mock next door:
jest.mock("../src/Icon");
If you want all tests to benefit from this file, put this in your Jest setup file, or just pick on a test-by-test basis. Either way, mocking is a powerful tool in your toolset, and now you can mock components.