2

I'm writing test with Jest and want to test a simple button that serves as logout and redirect you to "/login" via onClick.

import {loginService} from "../../utils/loginService";
import React from "react";
import {useNavigate} from "react-router-dom";

const Logout = () => {
    const navigate = useNavigate()

    const logout = e => {
        loginService.logout();
        navigate("/login");
    }

    return (
        <button className={'logout'} onClick={logout}>Logout</button>
    )
}

export default Logout

I was able to write a first test that renders the button and checks if it is there. A second test should check, that on click the browser redirects you to "/login".

import Logout from "../../components/navbar/Logout";
import {BrowserRouter} from "react-router-dom";
import {fireEvent, render} from "@testing-library/react";
import {screen} from '@testing-library/react';

describe('Test Logout Button', () => {
    it('it is rendered', () => {
        render(
            <BrowserRouter>
                <Logout/>
            </BrowserRouter>
        )
        expect(screen.getByText(/logout/i)).toBeInTheDocument()
    })

    it('it redirects after click', () => {
        const mockedUsedNavigate = jest.fn();

        jest.mock('react-router-dom', () => ({
            ...jest.requireActual('react-router-dom'),
            useNavigate: () => mockedUsedNavigate,
        }));

        render(
            <BrowserRouter>
                <Logout/>
            </BrowserRouter>
        )
        const logout = screen.getByText(/logout/i);

        fireEvent.click(logout);

        // TODO something something check useNavigate
        // https://blog.logrocket.com/testing-react-router-usenavigate-hook-react-testing-library/
        expect(mockedUsedNavigate).toBeCalledTimes(1);
    })
})

This however fails:

expect(jest.fn()).toBeCalledTimes(expected)

    Expected number of calls: 1
    Received number of calls: 0

I would assume that I would somehow have to pass the mock to my component. How would I do that or do I even have to do that? How do I check that useNavigate has redirected to "/login"? As far as I'm concerned <BrowserRouter /> does not have a history attribute.

Drew Reese
  • 165,259
  • 14
  • 153
  • 181
Peter
  • 1,844
  • 2
  • 31
  • 55
  • 1
    Don't mock what you don't own, and don't partially mock things. The answer below fixes the timing problem, but doesn't actually lead to high quality tests. The approach I illustrate here is far better practice: https://stackoverflow.com/a/65275037/3001761. – jonrsharpe May 15 '22 at 14:13

1 Answers1

4

jest.mock should be done outside of your unit tests as it needs to mock out a dependency before it is evaluated by the code you want to test. In your example, react-router-dom library is first evaluated by the Logout component and then your code attempts to mock it out which is too late. So you should move the following code outside of the describe:

const mockedUsedNavigate = jest.fn();

jest.mock('react-router-dom', () => ({
    ...jest.requireActual('react-router-dom'),
    useNavigate: () => mockedUsedNavigate,
}));

Of course, all of your tests would then be using the same instance of mockedUsedNavigate which can be error prone, so you should reset it before each unit test by adding it to a beforeEach as follows:

beforeEach(() => {
  mockedUsedNavigate.mockReset();
});
Drew Reese
  • 165,259
  • 14
  • 153
  • 181
Ovidijus Parsiunas
  • 2,512
  • 2
  • 8
  • 18