How to mock a custom hook using react-testing-library and jest

Viewed 619

I am trying to write tests for an authentication form I created. I have the need to mock the return value of a custom hook which returns the login function and the state showing if the login function is running and waiting for a response. I have to mock the useAuth() hook in the top of the component. I've never done testing before so I feel a bit lost!

Here is my Form:

export const LoginForm = () => {
  const { login, isLoggingIn } = useAuth(); // here's the function I want to mock
  const { values, onChange }: any = useForm({});
  const [error, setError] = useState(null);
  const handleLogin = async (e: React.FormEvent<HTMLFormElement>) => {
    e.preventDefault();
    try {
      await login(values);
    } catch (err: any) {
      setError(err.message);
    }
  };
  return (
    <Box
      sx={{
        py: (t) => t.spacing(8),
        px: (t) => t.spacing(8),
        background: '#FFF',
        borderRadius: 2,
        marginTop: 0,
        position: 'relative',
        zIndex: 1,
      }}
    >
      <form data-testid='login-form' onSubmit={handleLogin}>
        <FormTextField
          placeholder='your email'
          name='email'
          data-testid='login-email'
          onChangeFn={onChange}
          loading={isLoggingIn}
          fullWidth={true}
          id='email'
          label='email'
          type='text'
          autoFocus={true}
        />
        <FormTextField
          placeholder='password'
          name='password'
          data-testid='login-password'
          onChangeFn={onChange}
          loading={isLoggingIn}
          fullWidth={true}
          id='password'
          label='password'
          type='password'
          autoFocus={false}
          sx={{
            marginBottom: 2,
          }}
        />
        <Typography
          variant='subtitle1'
          sx={{
            marginY: (t) => t.spacing(3.7),
            textAlign: 'center',
          }}
        >
          Forget password? <BlueLink to='#/hello' text='Reset here' />
        </Typography>
        <FormButton
          text='Submit'
          data-testid='submit-login'
          variant='contained'
          disabled={false}
          fullWidth
          type='submit'
          sx={{
            color: 'white',
          }}
        />
        <div data-testid='error-login'>
          {error && (
            <>
              <hr />
              {error}
            </>
          )}
        </div>
      </form>
      <Hidden mdDown>
        <img
          style={{
            position: 'absolute',
            right: -50,
            bottom: -50,
          }}
          src={SquigglesOne}
          alt=''
        />
      </Hidden>
    </Box>
  );
};
export default LoginForm;

Here's how I'm trying to mock it:

jest.mock('../../../helpers/auth', () => ({
  ...jest.requireActual('../../../helpers/auth'),
  useAuth: jest.fn(() => ({
    login: () => true,
  })),
}));

When I run my tests I get an error: Error: Uncaught [TypeError: Cannot destructure property 'login' of '(0 , _auth.useAuth)(...)' as it is undefined.] But when I remove the mock my test runs successfully so I hope I'm close. Here's my full test:

import {
  fireEvent,
  render /* fireEvent */,
  within,
} from '@testing-library/react';
import { AuthProvider } from 'helpers/auth';
import { ReactQueryProvider } from 'helpers/react-query';
import { MemoryRouter } from 'react-router';
import LoginForm from './index';

jest.mock('../../../helpers/auth', () => ({
  ...jest.requireActual('../../../helpers/auth'),
  useAuth: jest.fn(() => ({
    login: () => true,
  })),
}));
const setup = () => {
  const loginRender = render(
    <MemoryRouter>
      <ReactQueryProvider>
        <AuthProvider>
          <LoginForm />
        </AuthProvider>
      </ReactQueryProvider>
    </MemoryRouter>
  );
  const emailbox = loginRender.getByTestId('login-email');
  const passwordBox = loginRender.getByTestId('login-password');
  const emailField = within(emailbox).getByPlaceholderText('your email');
  const passwordField = within(passwordBox).getByPlaceholderText('password');
  const form = loginRender.getByTestId('login-form');
  return {
    emailField,
    passwordField,
    form,
    ...loginRender,
  };
};

test('Input should be in document', async () => {
  const { emailField, passwordField } = setup();
  expect(emailField).toBeInTheDocument();
  expect(passwordField).toBeInTheDocument();
});

test('Inputs should accept email and password strings', async () => {
  const { emailField, passwordField } = setup();
  emailField.focus();
  await fireEvent.change(emailField, {
    target: { value: 'atlanteavila@gmail.com' },
  });
  expect(emailField.value).toEqual('atlanteavila@gmail.com');
  passwordField.focus();
  await fireEvent.change(passwordField, {
    target: { value: 'supersecret' },
  });
  expect(passwordField.value).toEqual('supersecret');
  await fireEvent.keyDown(form, { key: 'Enter' });
});

test('Form should be submitted', async () => {
  const { emailField, passwordField, form } = setup();
  emailField.focus();
  await fireEvent.change(emailField, {
    target: { value: 'atlanteavila@gmail.com' },
  });
  expect(emailField.value).toEqual('atlanteavila@gmail.com');
  passwordField.focus();
  await fireEvent.change(passwordField, {
    target: { value: 'supersecret' },
  });
  await fireEvent.keyDown(form, { key: 'Enter' });
  expect(passwordField.value).toEqual('supersecret');
});
0 Answers
Related