I recently had a troublesome 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 involves modifying the global object to add fetch
,
but also mocking every call to fetch
so it returns what we want,
in this case icons.
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.
Mocking ES Modules
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 <></>;
},
};
});
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.
Jest Mock Files
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");
Of course you can put this in your spec_helper.ts
if you want all tests to benefit from it,
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.