Paymahn Moghadasian
Paymahn Moghadasian

Reputation: 10329

Mocking library hook with Jest

Setup

I'm using a library called react-firebase-hooks in a component of mine.

// Component.tsx
import React, {useEffect} from 'react';
import { Firebase } from '../firebase/firebase';
import { useAuthState } from 'react-firebase-hooks/auth';


export const Component = () => {
  // Note that Firebase.getAuth is a static method of a class of mine for getting the firebase authentication object
  const [user, loading ] = useAuthState(Firebase.getAuth());


  if (!loading && !user) {
    // ... stuff
  }

  return (
    <div>

      ...stuff
    </div>
  );
};

A way that works

I'm trying to mock out the useAuthState call in one of my tests and I'm having a lot of trouble figuring out the optimal way to perform the mock. I've found a solution that works:

// Component.test.tsx
import {useAuthState} from 'react-firebase-hooks/auth'

jest.mock('react-firebase-hooks/auth', () => ({
  useAuthState: jest.fn()
}));

However, the above mocking implementation makes it impossible to get IDE completion for the mock objects. For example, here's me trying to set up a mock return value:

      test(`component correctly loads`, () => {
         useAuthState.mockReturnValue([true, false])

        // renderApp is a utility method of mine to render using react-testing-library
        const { getByText } = renderApp();

        // expect some stuff
      });

My preferred outcome (and attempt 1)

What I'm finding is that I don't get autocomplete on useAuthState.

Ideally how I'd like to declare the mock would be something closer to

const mockAuth = jest.fn();
jest.mock('react-firebase-hooks/auth', () => {
  return jest.fn().mockImplementation(() => {
    return {useAuthState: mockAuth}
  })
});

which would then let me alter the mockAuth object nicely through the tests I declare (and get autocomplete), eg:

      test(`component correctly loads`, () => {
         mockAuth.mockReturnValue([true, false])

        // renderApp is a utility method of mine to render using react-testing-library which renders Component
        const { getByText } = renderApp();

        // expect some stuff
      });

However, with the above implementation, I get the following error: (0 , _auth.useAuthState) is not a function or its return value is not iterable in Component.

Attempt 2

I've also tried declaring the mock as

const mockAuth = jest.fn();
jest.mock('react-firebase-hooks/auth', () => ({
    useAuthState: mockAuth
}));

but that gives me an error of ReferenceError: Cannot access 'mockAuth' before initialization.

Attempt 3

I've also tried

const mockAuth = jest.fn();
import {useAuthState} from 'react-firebase-hooks/auth'
jest.mock('react-firebase-hooks/auth', () => ({
    useAuthState: mockAuth
}));

but that gives the same error.

Closing question

Is there a way for me to declare a mock function "outside" of the mock declaration so that I can get autocomplete for it?

Upvotes: 0

Views: 1551

Answers (1)

evelynhathaway
evelynhathaway

Reputation: 1887

If you use another file for the mock, you should be golden to change the implementation and still preserve the TypeScript Intellisense.

src/App.test.jsx

import App from "../App";
import { render } from "@testing-library/react";
import { useAuthState } from "../__mocks__/react-firebase-hooks/auth";

it("should be mocked", () => {
  // Change the implementation of the mock
  useAuthState.mockReturnValue([true, false]);
  const { getByText } = render(<App />);
  // Expect that the app file got the mocked value
  expect(useAuthState).toBeCalledWith("my auth");
  expect(getByText("true,false")).toBeTruthy();
});

src/__mocks__/react-firebase-hooks/auth.js

export const useAuthState = jest.fn();

src/App.js

import { useAuthState } from "react-firebase-hooks/auth";

export default function App() {
  const test = useAuthState("my auth");
  return <div>{test.toString()}</div>;
}

Upvotes: 1

Related Questions