- Published on
A Three Step Waltz for Mocking React Hooks with React Testing Library and TypeScript
- Authors
- Name
- Teal Larson
- @teallarson
Testing Hooks: The Three Step Waltz
Sometimes, you need to test a component that depends upon a hook. Reasonably, you probably want to mock out different return values within that suite. While the concept is straightforward, I've found the solution to be somewhat unintuitive. In fact, while I figured this pattern out over a year ago, I still have to find my old code every time to remember how to implement it.
I call it a Three Step Waltz:
- Mock the file with your hook in it
- Create a mock of the function itself + perform some type sorcery (one line of code so we'll call it one step 😉)
- MockImplementationOnce in your tests Optional: I like to include a "base case" mock return so that my test code only contains the variables I want to pay attention to + the rest can be handled via a spread operator. In my opinion, this makes it easier to tell what exactly is being tested where, as well as reducing human error.
Let's break down an example of this in practice. Here, I am testing a component called RunButton
that starts an async process. I have a hook called useRunSomeFunc
that triggers and tracks the process' state. I'd like to make sure that my button's content changes correctly based upon a few different hooks (one being this useRunSomeFunc
):
jest.mock("hooks/useRunSomeFunc"); // 1. Mock the file
const mockUseRunSomeFunc = useRunSomeFunc as unknown as jest.Mock<Partial<typeof useRunSomeFunc>>; // 2. Create a mock of the function with some type sorcery
const baseHookValues = { // the optional base case
completed: false
runAsyncFunc: jest.fn(),
loading: false,
};
"Teal," you're saying, "that's great, but you said three steps. Have you forgotten how to count? That's only two!" Ah ha, the third step comes in the actual tests where you can now manipulate this hook to your heart's content.
Example:
describe("RunButton", () => {
beforeEach(() => jest.clearAllMocks())
it("Shows the Incomplete label if completed is false", () => {
mockUseRunSomeFunc.mockImplementationOnce(() => ({
...baseHookValues
completed: false
}));
render(
<RunButton />
);
const button = screen.getByRole("button", { name: "Incomplete" });
expect(button).toBeInTheDocument();
})
it("Shows the Run Again label if completed is true", () => {
mockUseRunSomeFunc.mockImplementationOnce(() => ({
...baseHookValues
completed: true
}));
render(
<RunButton />
);
const button = screen.getByRole("button", { name: "Run Again" });
expect(button).toBeInTheDocument();
})
})
Et voila!
If you'd like to learn more about testing with React, here are a few resources I've found useful:
- Testing React with Jest and React Testing Library (RTL) -- Bonnie's class is great! My tests and my overall approach to testing improved quite a bit after taking this.
- React Testing Library "Cheat Sheet" -- Because it's hard to remember whether you want to
query
orget
... or what you canquery
orget
by!