Testing components which use withRouter HOC React

Testing components which use withRouter HOC React

Introduction

Usually, if you are using react-router, then you would be using withRouter to access the match, location and history. withRouter HOC is a higher order component that provides the child component the route parameters like match, location and history as props. We are going to write three test cases to test different aspects of the below sample component, and showcase the following points

  • Testing the component using MemoryRouter
  • Passing custom pathname to the component
  • Wrapping component with Router to access change in pathname

Sample Component

import React, { Component } from "react";
import { withRouter } from "react-router";

/* Test component is child of some parent component which renders on /app route, so you have to use withRouter HOC to access the history prop. */
class Test extends Component {
  updateRoute = () => {
    this.props.history.push("/app/1");
  };

  render() {
    return (
      <div>
        <h3>Location</h3>
        <div data-testid="pathname">{this.props.location.pathname}</div>
        <button onclick="{this.updateRoute}">Update Route</button>
      </div>
    );
  }
}

export default withRouter(Test);

Testing the component using MemoryRouter

Test File

it("should render title", () => {
  const wrapper = render(
    <Test/>
  );
  expect(wrapper.queryAllByText("Location")).toHaveLength(1);
});
it("should render title", () => {
  const wrapper = render(
    <Test/>
  );
  expect(wrapper.queryAllByText("Location")).toHaveLength(1);
});

When you try to test it, you would get the error

Invariant failed: You should not use <withrouter(Test)> outside a <Router>

We can overcome this error by simply wrapping it with MemoryRouter provided by react-router-dom

import React from "react";
import { render } from "@testing-library/react";
import Test from "./Test";
import { MemoryRouter } from "react-router-dom";

describe('Test', () => {
  it("should render title", () => {
    const wrapper = render(
      <MemoryRouter>
        <Test/>
     </MemoryRouter>
    );
    expect(wrapper.queryAllByText("Location")).toHaveLength(1);
  });
})

Passing custom pathname to the component

Sometimes, we need to pass custom pathname to component, when the component depends upon the id or name passed from the url.

We can use MemoryRouter and pass initialEntries to it. This will set the pathname for the component. In the sample component, we render the pathname in the test id “pathname”.

This is the test case for the above part

import React from "react";
import { render } from "@testing-library/react";
import Test from "./Test";
import { MemoryRouter } from "react-router-dom";

describe('Test', () => {
  it("should render the pathname", () => {
    const wrapper = render(
      <MemoryRouter initialentries="{['/app/2']}">
        <Test />
     </MemoryRouter>
    );
    expect(wrapper.queryByTestId("pathname").textContent).toEqual("/app/2");
  });
})

Wrapping component with Router to access change in pathname

In our sample component, on click of the button Update Route, we update the route to /app/1. Here we need to access the history object to test the route change.

So MemoryRouter is not sufficient for this task.

We are going to use the Router from react-router and pass custom history object to it. Then we can check the change in pathname from the history object.

For this the test environment should support window.history.

Test file

import React from "react";
import { render } from "@testing-library/react";
import Test from "./Test";
import { Router } from "react-router";
import { createMemoryHistory } from "history";

describe('Test', () => {
  it("should update the route on click of the button",() => {
    const history = createMemoryHistory();
    const wrapper = render(
      <Router history="{history}">
        <Test/>
     </Router>
    );
    wrapper.getByText("Update Route").click();
    expect(history.location.pathname).toBe("/app/1");
  });
});

Conclusion

So we learnt that if you want to simply pass some parameters from the URL to the component, then you can test it by wrapping the component with MemoryRouter.

If you component changes the route on some action, then using MemoryRouter is not sufficient because you cannot access the change in history object using MemoryRouter. In this scenario, use the Router and pass custom history object to it. Then you can easily verify the change in history object in you test as shown above.

%d bloggers like this: