Unit test for React component containing useRouteMatch

Having the following component:

import React, { useState, useEffect } from 'react';
import { createPortal } from 'react-dom';
import { useRouteMatch, Link } from 'react-router-dom';

interface MyComponentProps {
  myId?: string;
  link?: string;
}

export const MyComponent: React.FunctionComponent<MyComponentProps> = ({
  myId = 'default-id',
  link,
  children
}) => {
  const [myOutlet, setMyOutlet] = useState<HTMLOListElement>();
  const match = useRouteMatch();

  useEffect(() => {
    const outletElement = document.getElementById(myId) as HTMLOListElement;
    if (outletElement) {
      setMyOutlet(outletElement);
    }
  }, [myId]);

  if (!myOutlet) {
    return null;
  }

  return createPortal(
    <li>
      <Link to={link || match.url}>{children}</Link>
    </li>,
    myOutlet
  );
};

export default MyComponent;

I want to write unit tests using React Testing Library for it, the problem is that it keeps throwing an error because of useRouteMatch.

Here is my code:

import { render, screen } from '@testing-library/react';

import { MyComponent } from './my-component';

describe('MyComponent', () => {
  const testId = 'default-id';
  const link = '/route';
  it('should render MyComponent successfully', () => {
    const element = render(<MyComponent myId={testId} link={link} />);
    expect(element).toBeTruthy();
  });
});

The error appears at the line with const match = useRouteMatch();, is there a way to include this part in the test?

>Solution :

You should use <MemoryRouter>:

A <Router> that keeps the history of your “URL” in memory (does not read or write to the address bar)

Provide mock locations in the history stack by using the initialEntries props.

Then, use <Route> component to render some UI when its path matches the current URL.

The following example, assuming that the location pathname in the browser current history stack is /one, <Route>‘s path prop is also /one, The two matching, rendering MyComponent.

E.g.

my-component.tsx:

import React, { useState, useEffect } from 'react';
import { createPortal } from 'react-dom';
import { useRouteMatch, Link } from 'react-router-dom';

interface MyComponentProps {
  myId?: string;
  link?: string;
}

export const MyComponent: React.FunctionComponent<MyComponentProps> = ({ myId = 'default-id', link, children }) => {
  const [myOutlet, setMyOutlet] = useState<HTMLOListElement>();
  const match = useRouteMatch();
  console.log('match: ', match);

  useEffect(() => {
    const outletElement = document.getElementById(myId) as HTMLOListElement;
    if (outletElement) {
      setMyOutlet(outletElement);
    }
  }, [myId]);

  if (!myOutlet) {
    return null;
  }

  return createPortal(
    <li>
      <Link to={link || match.url}>{children}</Link>
    </li>,
    myOutlet
  );
};

export default MyComponent;

my-component.test.tsx:

import React from 'react';
import { render, screen } from '@testing-library/react';

import { MyComponent } from './my-component';
import { MemoryRouter, Route } from 'react-router-dom';

describe('MyComponent', () => {
  const testId = 'default-id';
  const link = '/route';
  it('should render MyComponent successfully', () => {
    const element = render(
      <MemoryRouter initialEntries={[{ pathname: '/one' }]}>
        <Route path="/one">
          <MyComponent myId={testId} link={link} />
        </Route>
      </MemoryRouter>
    );
    expect(element).toBeTruthy();
  });
});

test result:

 PASS  examples/70077434/my-component.test.tsx (8.433 s)
  MyComponent
    ✓ should render MyComponent successfully (46 ms)

  console.log
    match:  { path: '/one', url: '/one', isExact: true, params: {} }

      at MyComponent (examples/70077434/my-component.tsx:13:11)

------------------|---------|----------|---------|---------|-------------------
File              | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s 
------------------|---------|----------|---------|---------|-------------------
All files         |    87.5 |    28.57 |     100 |   86.67 |                   
 my-component.tsx |    87.5 |    28.57 |     100 |   86.67 | 18,26             
------------------|---------|----------|---------|---------|-------------------
Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   0 total
Time:        8.951 s, estimated 9 s

package versions:

"react": "^16.14.0",
"react-router-dom": "^5.2.0"

Leave a Reply