Reputation: 33
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
import React, { createContext, useContext, useEffect, useState } from 'react';
import PropTypes from 'prop-types';
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
import React from 'react';
import { render, fireEvent, screen } from '@testing-library/react';
import { NavBar } from '../App';
jest.resetAllMocks();
jest.mock('../auth/useAuth', () => {
const originalModule = jest.requireActual('../auth/useAuth');
return {
__esModule: true,
...originalModule,
default: () => ({
accessToken: 'token',
isAuthenticated: true,
login: jest.fn,
logout: jest.fn
})
};
});
describe('NavBar when isAuthenticated', () => {
it('LogOut button is visible when isAuthenticated', () => {
render(<NavBar />);
expect(screen.getByText(/log out/i)).toBeVisible();
});
});
The function I'm writing tests on:
//App.js
import React from 'react';
import cn from 'classnames';
import useAuth, { AuthProvider } from './auth/useAuth';
import './App.css';
import '../node_modules/bootstrap/dist/css/bootstrap.css';
function App() {
return (
<AuthProvider>
<Router>
<NavBar />
</Router>
</AuthProvider>
);
}
const NavBarSignUpButton = () => (
<button className='button info'>
Sign up
</button>
);
const NavBarLogoutButton = () => {
const auth = useAuth();
const handleLogOut = () => {
auth.logout();
};
return (
<button className='button info' onClick={handleLogOut}>
Log out
</button>
);
};
export const NavBar = () => {
const isAuthenticated = useAuth().isAuthenticated;
const loginButtonClassName = cn({
btn: true,
invisible: isAuthenticated
});
return (
<nav className='navbar navbar-expand-lg navbar-light'>
<div className='container'>
<div className='d-flex justify-content-end'>
<div className='navbar-nav'>
<button className={loginButtonClassName}>
Log In
</button>
{isAuthenticated ? <NavBarLogoutButton /> : <NavBarSignUpButton />}
</div>
</div>
</div>
</nav>
);
};
The test code above doesn't throw any errors. However, the test fails as useAuth().isAuthenticated is always false (but I'm mocking it to return true). It doesn't change whether I test App or only NavBar
What am I doing wrong?
Upvotes: 1
Views: 5106
Reputation: 6201
I made a super minified example that should show the mocking works. It just features the hook itself and a component returning YES
or NO
based on the hook. The test
useAuth.js
import {createContext, useContext} from 'react'
const authContext = createContext()
const useAuth = () => {
return useContext(authContext)
}
export default useAuth
component.js
import useAuth from './useAuth'
export const Component = () => {
const isAuthenticated = useAuth().isAuthenticated
return isAuthenticated ? 'YES' : 'NO'
}
component.test.js
import React from 'react'
import {render, screen} from '@testing-library/react'
import {Component} from './component'
jest.mock('./useAuth', () => {
const originalModule = jest.requireActual('./useAuth')
return {
__esModule: true,
...originalModule,
default: () => ({
accessToken: 'token',
isAuthenticated: true,
login: jest.fn,
logout: jest.fn,
}),
}
})
describe('When isAuthenticated', () => {
it('Component renders YES', () => {
render(<Component />)
screen.getByText(/YES/i)
})
})
In this case, the component does in fact render YES
and the test passes. This makes me thing there are other things involved. When I change the mock to false
, the test fails because it renders NO
.
Upvotes: 2