Search code examples
typescriptreact-nativejestjsexporeact-native-auth0

How should I test useAuth0().authorize in Jest for Expo app in React Native


I've looked around for answers to this but can't find any solutions regarding this topic.

Essentially, I'm trying to write some unit tests using '@testing-library/react-native' to test that useAuth0().authorize is called with a press of my login button. I'm expecting it to have been called 1 time but receive 0 calls back.

What am I doing wrong?

Here is what the landing page looks like in ./app/(login)/index.tsx:

import { StyleSheet, TouchableOpacity, SafeAreaView } from 'react-native';
import { useAuth0 } from 'react-native-auth0';

export default function Landing(): React.JSX.Element {
  const { authorize, clearSession, user } = useAuth0();

  const onLogin = async () => {
    try {
      await authorize();
    } catch (e) {
      console.log(e);
    }
  };

  const loggedIn = user !== undefined && user !== null;

  const onLogout = async () => {
    try {
      await clearSession();
    } catch (e) {
      console.log('Log out cancelled');
    }
  };

  return (
    <SafeAreaView style={styles.container}>
      <TouchableOpacity style={styles.logBtn} onPress={loggedIn ? onLogout : onLogin} testID='loginBtn'>
        {user && <Text style={styles.btnTxt}>Log Out</Text>}
        {!user && <Text style={styles.btnTxt}>Log In</Text>}
      </TouchableOpacity>
    </SafeAreaView>
}

Here is the test I've landed at:

import { render, fireEvent, waitFor } from '@testing-library/react-native';
import { useAuth0 } from 'react-native-auth0';
import Landing from '@/app/(login)/index';


jest.mock('react-native-auth0', () => ({
  useAuth0: jest.fn(() => ({
    authorize: jest.fn(),
  })),
}));

describe('Login page renders and performs correct actions through Auth0', () => {
  test('Login button correctly calls authorize function', async () => {
     const { getByTestId } = render(<Landing />);
     const loginBtn = getByTestId('loginBtn');
      
     fireEvent.press(loginBtn)

     await waitFor(() => {
       expect(useAuth0().authorize).toHaveBeenCalledTimes(1);
     })
  })
})

Here is the error I receive:

expect(jest.fn()).toHaveBeenCalledTimes(expected)

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

      43 |       fireEvent.press(loginBtn)
      44 |
    > 45 |       await waitFor(() => {
         |                    ^
      46 |         expect(useAuth0().authorize).toHaveBeenCalledTimes(1);
      47 |       })
      48 |     })

A couple other things I tried:

Switching:

fireEvent.press(loginBtn); to fireEvent(loginBtn, 'click');

<TouchableOpacity></TouchableOpacity> to <Button></Button>

These did not work either :(


Solution

  • The problem here is that you're referencing a new mock in your test, not the same one as will be referenced in your component.

    To get this to work you will want to use a mocked function that you pass into the mocked module.

    For example, I have a very similar scenario I've dealt with using React Router's useNavigate hook:

    const mockUseNavigate = jest.fn();
    
    jest.mock("react-router", () => ({
      ...jest.requireActual("react-router"),
      useNavigate: () => mockUseNavigate,
    }));
    

    This can then be used in one of two ways:

    const useNavigateMock = useNavigate();
    // do stuff to trigger the nav
    expect(useNavigateMock).toHaveBeenCalledWith("/");
    

    or you can use the mock directly:

    expect(mockUseNavigate).toHaveBeenCalledWith("/");
    

    The downside of the first method is that you need to import and invoke the hook in each test, the downside of the second method is that you're using a mock defined outside of the scope of the test/describe. The important thing regardless of which method you use is that you end up with a beforeEach to reset the mocks or set resetMocks: true in your Jest config.

    In your case you can of course update your useAuth0() mock to return an object to match the shape of the return value of the hook. e.g.

    const mockAuthorize = jest.fn();
    jest.mock('react-native-auth0', () => ({
      useAuth0: () => ({
        authorize: mockAuthorize,
      }),
    }));
    

    (I remove the wrapping jest.fn() as well, as you probably don't need that unless you're asserting on usage of the hook itself)