Mocking 3rd Party Components With Jest
React + Jest + Reacting Testing Library + 3rd Party Component Mocking
I recently ran into an issue where I was trying to test a component that contained a 3rd party component. The child component was more complex than a simple input (as third party components are), and I needed more control over the way it was mocked. I’m fairly new to Jest testing, and I could not find a suitable example for how to do this. So here is what I discovered.
Getting Started
My assumption is that you are already familiar with React and testing, so I’m not going to go into much detail about that. I am working on app created with create-react-app
so everything for Jest is configured according to their defaults. Other than that, here is the version of React Testing Library we are working with:
"@testing-library/react" : "^10.4.1"
For this particular solution, I’m not sure any of the other packages matter.
3rd Party Component
The component I needed to create a mock for is a shared component we have on https://bit.dev. See here.
Our component index.d.ts
looks like this:
import {Component} from "react";
interface IndustrySearchProps {
selectIndustry: (industryId: number, industryName: string) => void,
placeholder?: string,
industryId?: string,
industryName?: string,
reportError?: (error: string) => void,
industrySearch?: (term: string) => Promise<[object]>,
loadingElement?: JSX.Element,
className?: string,
headerText?: string
}
declare class IndustrySearch extends Component<IndustrySearchProps, any> {}
export default IndustrySearch;
The purpose of this component is to interface with a 3rd party API and return results back for selection. The only required prop is the selectIndustry
function which has two parameters: industryId: number
and industryName: string
. We use this component in several places in our code base.
The Problem
I need to write a test for a parent component that includes the above IndustrySearch
component. How do I do this? First, I tried ignoring the problem, hoping it would just work.
Nope. All kinds of errors relating to information the IndustrySearch
component needed that it was not getting.
Solution #1 — the simple mock
I reached out to a co-worker about how to mock the component and got the following suggestion:
jest.mock("@bit/coterie.partner-ui.industry-search", () => () => <input />);
That seemed simple enough. I dropped it at the top of my spec.js
file and Boom! Everything worked for my test. Problem solved. Tests passing. Go on a lunch break.
After Lunch Problems
As with all problems you solve prior to lunch, they quickly become unsolved when you return.
The mock worked well for the case where I did not need to interact with the component. But, that input is a required field in our form and it changes the state of the parent component when the user makes a selection. So, how to handle that?
Solution #2 — complex mockery
After much fumbling around, I came across the concept of Manual Mocks in the Jest documentation: https://jestjs.io/docs/en/manual-mocks
Essentially, you can create a __mocks__
folder inside of your src
. Any components you create in here, will be substituted for their actual versions when Jest tests are run.
For instance, let’s say you want to mock axios
. Simple. Create axios.js
in src/__mocks__
. Here is what ours looks like:
const mockAxios = jest.genMockFromModule("axios");
mockAxios.create = jest.fn(() => mockAxios);
export default mockAxios;
Now, any components that import axios
will receive this mocked version instead. Magic.
So, back to our IndustrySearch
. How can I do the same for it? First, the name is weird: @bit/coterie.partner-ui.industry-search
. How do I create a file for that? Lucky for me, my editor is more intelligent than I am. I went to create a new file in the __mocks__
folder with that name and it did the work for me, creating a directory called @bit
with a file in it name coterie.partner-ui.industry-search.js
.
Now we’re getting somewhere. I created a mock version of the component in the file:
import React from "react";
const IndustrySearch = ({ selectIndustry }) => {
const handleOnChange = () => {
selectIndustry(10001, "IndustryName");
};
return <input data-testid={"industry-search"} onChange={handleOnChange} />;
};
export default IndustrySearch;
And voilà! It does what I need. When I use React Testing Library to type in that input field, it calls handleOnChange
which fires the call to selectIndustry
with a default industry Id and name, updating the state of the parent component.
Every Jest test I now create for a component that imports @bit/coterie.partner-ui.industry-search
will now pull from this mocked version instead, giving me the control I need.
Thanks for reading! Would love to hear any thoughts or feedback!