This tutorial shows you how to avoid using jest.mock when making components route-aware by using the react-router useHistory Hook.

React-router version 5 introduced a new family of Hooks that have simplified the process of making components route-aware.

useHistory does a great job of showcasing React Hooks by separating access to the react-router history object into a cross-cutting concern and replaces the previous cumbersome access via higher-order components or render-props that are now a thing of the past.

javascript
import { useHistory } from "react-router-dom";

function HomeButton() {
  let history = useHistory();

  function handleClick() {
    history.push("/home");
  }

  return (
    <button type="button" onClick={handleClick}>
      Go home
    </button>
  );
}

The new Hook makes life much more comfortable, but how do I write tests for this Hook? My initial googling brought back stackoverflow posts like this that advocate for using jest.mock.

javascript
jest.mock('react-router-dom', () => ({
  ...jest.requireActual('react-router-dom'),
  useHistory: () => ({
    push: jest.fn()
  })
}));

I try and avoid this approach at all costs since I want my tests to simulate real usage. jest.mock will blitz an essential component from the simulated reality and I might miss some critical regressions with this approach.

Instead, I am going to lean heavily on react router’s MemoryHistory.

Sample application

I have created this codesandbox:

This includes a simple Hook called useStepper that allows the user to navigate forward and back through several application steps:

A screenshot of a sample app.

Each forward or backward navigation uses the history object returned from useHistory to navigate to a new component at a new url:

javascript
export const useStepper = () => {
  const history = useHistory();

  const nextStepAction = useCallback(() => {
    setCurrentStep(index + 1);
  }, [getCurrentStepIndex]);

  const previousStepAction = useCallback(() => {
    setCurrentStep(index - 1);
  }, [getCurrentStepIndex]);

  useEffect(() => {
    const { path } = currentSteps[currentStep];

    history.push({
      pathname: path,
      state: { previousPath: history.location.pathname }
    });
  }, [currentStep, history]);

  // rest
};

There now follows a few easy steps to take control of useHistory without jest.mock.

#react #testing #developer

How to use the react-router useHistory Hook with React Testing Library
104.60 GEEK