1

I need to mock my custom hook when unit testing React component. I've read some stackoverflow answers but haven't succeeded in implementing it correctly.

I can't use useAuth without mocking it as it depends on server request and I'm only writing unit tests at the moment.

//useAuth.js - custom hook

const authContext = createContext();

function useProvideAuth() {
    const [accessToken, setAccessToken] = useState('');
    const [isAuthenticated, setAuthenticated] = useState(
        accessToken ? true : false
    );

    useEffect(() => {
        refreshToken();
    }, []);

    const login = async (loginCredentials) => {
        const accessToken = await sendLoginRequest(loginCredentials);
        if (accessToken) {
            setAccessToken(accessToken);
            setAuthenticated(true);
        }
    };

    const logout = async () => {
        setAccessToken(null);
        setAuthenticated(false);
        await sendLogoutRequest();
    };

    const refreshToken = async () => {
        const accessToken = await sendRefreshRequest();
        if (accessToken) {
            setAccessToken(accessToken);
            setAuthenticated(true);
        } else setAuthenticated(false);
        setTimeout(async () => {
            refreshToken();
        }, 15 * 60000 - 1000);
    };

    return {
        isAuthenticated,
        accessToken,
        login,
        logout
    };
}

export function AuthProvider({ children }) {
    const auth = useProvideAuth();

    return <authContext.Provider value={auth}>{children}</authContext.Provider>;
}

AuthProvider.propTypes = {
    children: PropTypes.any
};

const useAuth = () => {
    return useContext(authContext);
};

export default useAuth;

The test I've written

//mainPage.test.js

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

describe('Main when !isAuthenticated', () => {
    beforeEach(() => {
        jest.mock('../auth/useAuth', () => {
            const originalModule = jest.requireActual('../auth/useAuth');
            return {
                __esModule: true,
                ...originalModule,
                default: () => ({
                    isAuthenticated: false,
                    login: jest.fn,
                    logout: jest.fn
                })
            };
        });
    });

    afterEach(() => {
        jest.resetModules();
    });

    it('displays image and login form', () => {
        render(<Main />);

        const image = screen.getByRole('img');
        const form = document.querySelector('[data-testid=loginForm]');
        expect(image).toBeInTheDocument();
        expect(form).toBeInTheDocument();
    });
});

However, I get this error.

 TypeError: Cannot read properties of undefined (reading 'isAuthenticated')

       7 |
       8 | function Main() {
    >  9 |      const isAuthenticated = useAuth().isAuthenticated;
         |                              ^
      10 |      const location = useLocation();
      11 |
      12 |      if (isAuthenticated)

      at Main (src/main/mainPage.js:9:26)
      at renderWithHooks (node_modules/react-dom/cjs/react-dom.development.js:14985:18)...

I've been also trying to use spyOn but nothing helped. What exactly do I need to change to make the mock work?

a.baulina
  • 23
  • 2
  • 5

1 Answers1

3

The mocking should happen before any describe block:

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

jest.mock('../auth/useAuth', () => {
    const originalModule = jest.requireActual('../auth/useAuth');
    return {
        __esModule: true,
        ...originalModule,
        default: () => ({
            isAuthenticated: false,
            login: jest.fn,
            logout: jest.fn
        })
    };
});

describe('Main when !isAuthenticated', () => {

Jakub Kotrs
  • 5,823
  • 1
  • 14
  • 30
  • Hi, @Jakub. Could you help me again? I'm facing similar issue in other test file. I'm also trying to mock useAuth and it doesn't throw error that time. However, the test fails as isAuthenticated returns false. I've posted it here https://stackoverflow.com/questions/71778604/mocking-react-custom-hook-return-value-as-a-module-with-jest-returns-wrong-value – a.baulina Apr 07 '22 at 08:18
  • @Jakub do you know how can I implement a different value for each test – Christian Herrejon Sep 05 '22 at 19:01
  • @ChristianHerrejon If you need different values, you need to use `jest.fn()` alongside `mockImplementation`. Comments are a limited place to describe it, but I'll try. Step 1: mock the default to be `default:: jest.fn()` instead of a function returning object. Step 2: import the mock `import useAuth from './useAuth'`. Step 3: mock the implementation with `useAuth.mockReturnValue(true)` or `useAuth.mockReturnValue(false)` inside the test. You can see it here: https://stackoverflow.com/a/45007792/2188587 – Jakub Kotrs Sep 06 '22 at 21:33
  • Please have a look at this i am facing same problem : https://stackoverflow.com/questions/73977384/the-module-factory-of-jest-mock-is-not-allowed-to-reference-any-out-of-scope – user2028 Oct 07 '22 at 08:44