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.
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
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:
Each forward or backward navigation uses the
history object returned from
useHistory to navigate to a new component at a new url:
There now follows a few easy steps to take control of
Step 1: Centralize the history object
I centralize all access to the history object into a single export from one file located at src/history/index.ts:
With this approach, I guarantee that all test and application code is dealing with the same
I usually keep conditionals such as
process.env.NODE_ENV === "test"; out of the application code, but I am making an exception in this case.
Step 2: Create a higher-order component to wrap any component under test in a Router
renderInRouter is a simple function that takes a component and wraps it in a router. The critical thing to note here is the
import in line 1:
As mentioned previously, this import will resolve to the central export that both the application code and test code now reference.
Step 3: Test components with
Testing components that use
useHistory will now be easy with the two previous steps in place.
The useStepper.test.tsx test references the same
history object as the application code to set up the test scenarios:
render function will call the
renderInRouter higher-order component and supply a component with routing for testing. The single history object that all code references is imported in the same way as the application code:
The react-hooks-testing-library allows us to test Hooks in isolation with the
useStepper Hook can now be tested without being invoked from a specific component.
The vital thing to note is that a wrapper option is supplied to the renderHook function. We need to wrap the Hook in a
Router to give the
useStepper Hook the correct context to allow the test to flow.
jest.mock is something to be avoided in my experience and gives a false illusion to tests. With the approach outlined in this article, you won’t need to do this.
LogRocket is like a DVR for web apps, recording literally everything that happens on your React app. Instead of guessing why problems happen, you can aggregate and report on what state your application was in when an issue occurred. LogRocket also monitors your app's performance, reporting with metrics like client CPU load, client memory usage, and more.
The LogRocket Redux middleware package adds an extra layer of visibility into your user sessions. LogRocket logs all actions and state from your Redux stores.
Modernize how you debug your React apps — start monitoring for free.