test back button behaviour in React Router v6

Viewed 285

i've got a react app, using react router, and react testing library for the tests. i'm in the process of upgrading from react router v5.1 to v6.

i've got a test that verifies that something happens (a modal is dismissed) when the browser back button is clicked. in the old version of the test i used createMemoryRouter from the history package to simulate the back button press, something like this:

const history = createMemoryHistory();

render(
  <Router history={history}>
    <Route path="/">
      <App />
    </Route>
  </Router>
);

// perform some setup

history.goBack();

// make assertions about state of application

but since v6 uses hooks to interact with the history it's not possible to pass a history object in like this. so what can i do to test my application's interaction with the back button?

1 Answers

You can create a custom MemoryRouter that allows you to pass in your own history object. Use the source MemoryRouter for refactoring purposes.

MemoryRouter

export function MemoryRouter({
  basename,
  children,
  initialEntries,
  initialIndex,
}: MemoryRouterProps): React.ReactElement {
  let historyRef = React.useRef<MemoryHistory>();
  if (historyRef.current == null) {
    historyRef.current = createMemoryHistory({ initialEntries, initialIndex });
  }

  let history = historyRef.current;
  let [state, setState] = React.useState({
    action: history.action,
    location: history.location,
  });

  React.useLayoutEffect(() => history.listen(setState), [history]);

  return (
    <Router
      basename={basename}
      children={children}
      location={state.location}
      navigationType={state.action}
      navigator={history}
    />
  );
}

Notice that the MemoryRouter maintains its own internal history reference. This is what we want to "expose".

Create a new custom router that takes a history prop instead.

const CustomRouter = ({ history, ...props }) => {
  const [state, setState] = React.useState({
    action: history.action,
    location: history.location,
  });

  React.useLayoutEffect(() => history.listen(setState), [history]);

  return (
    <Router
      {...props}
      location={state.location}
      navigationType={state.action}
      navigator={history}
    />
};

From here import your CustomRouter and pass it an instantiated memory history object.

Example:

import { createMemoryHistory } from 'history';
import Router from '../path/to/CustomRouter';

const history = createMemoryHistory();

...

render(
  <Router history={history}>
    <Route path="/">
      <App />
    </Route>
  </Router>
);

// perform some setup

history.goBack();

// make assertions about state of application
Related