Search code examples
reactjsjestjsreact-routerreact-testing-library

Unit Test NavLink Navigation in React


I am using React v18.2.0 and React Testing Library v5.17.0.

How should I unit test the NavLinks in my navigation bar?

I think this test should work, but I am getting the error:

Expected: "about"
Received: "about", {"preventScrollReset": undefined, "relative": undefined, "replace": false, "state": undefined, "unstable_viewTransition": undefined}

Looks like an object is being passed as well as the route and it is causing the test to fail?

My NavBar:

const NavBar = () => {

  return (
    <>
        <div className="navbar">
            <ul>
                <li>
                  <NavLink to="home" className="navlink">Home</NavLink>
                </li>
                <li>
                  <NavLink to="about" className="navlink">About</NavLink>
                </li>
            </ul> 
        </div>
    </>
  );
};

export default Navbar;

My unit test:

import { render, fireEvent } from "@testing-library/react";
import NavBar from './NavBar';
import * as router from 'react-router';
import { BrowserRouter as Router } from 'react-router-dom';

describe(NavBar, () => {
    const mockedNavigation = jest.fn();
    
    const MockedNav = () => {
        return(
            <Router>
                <Navbar />
            </Router>
        )
    }

    beforeEach(() => {
        jest.spyOn(router, 'useNavigate').mockImplementation(() => mockedNavigation)
    })
        
    it('should navigate to the about page', () => {
        const { getByText } = render(<MockedNav />);
        fireEvent.click(getByText(/About/i));

        expect(mockedNavigation).toHaveBeenCalledWith("about");
    });
})

Solution

  • Issue

    The function you are mocking is passed these other arguments.

    Solutions

    • You can simply add the additional arg to the test:

      describe(NavBar, () => {
        const mockedNavigation = jest.fn();
      
        beforeEach(() => {
          jest.spyOn(router, 'useNavigate').mockImplementation(() => mockedNavigation
        }
      
        it('should navigate to the about page', () => {
          const { getByText } = render(<Navbar />, { wrapper: Router });
          fireEvent.click(getByText(/About/i));
      
          expect(mockedNavigation).toHaveBeenCalledWith(
            "about",
            {
              preventScrollReset: undefined,
              relative: undefined,
              replace: false,
              state: undefined,
              unstable_viewTransition: undefined
            }
          );
        });
      })
      
    • You can assert the mock was called and that the first argument is what you expect:

      describe(NavBar, () => {
        const mockedNavigation = jest.fn();  
      
        beforeEach(() => {
          jest.spyOn(router, 'useNavigate').mockImplementation(() => mockedNavigation
        });
      
        it('should navigate to the about page', () => {
          const { getByText } = render(<Navbar />, { wrapper: Router });
          fireEvent.click(getByText(/About/i));
      
          expect(mockedNavigation).toHaveBeenCalled();
          expect(mockedNavigation.mock.calls[0][0]).toEqual("about");
        });
      });
      
    • Or you can use an asymmetric matcher to assert anything passed in the second arg (since it appears you don't care what that specific value is):

      describe(NavBar, () => {
        const mockedNavigation = jest.fn();
      
        beforeEach(() => {
          jest.spyOn(router, 'useNavigate').mockImplementation(() => mockedNavigation
        }
      
        it('should navigate to the about page', () => {
          const { getByText } = render(<Navbar />, { wrapper: Router });
          fireEvent.click(getByText(/About/i));
      
          expect(mockedNavigation).toHaveBeenCalledWith(
            "about",
            expect.anything(),
          );
        });
      })