Mocking method on default export class in Jest in Typescript

I prefer to create a service using dynamic links (or other firebase functions). It's easy to mock.

dynamicLinksService.ts

import dynamicLinks from '@react-native-firebase/dynamic-links';

export const getInitialLink = () => dynamicLinks().getInitialLink();

useDynamicLink.ts

import { useEffect } from 'react';

import { navigateFromBackground } from '../deeplink';

import { getInitialLink } from './dynamicLinkService';

export const useDynamicLink = (): void => {
  useEffect(() => {
    getInitialLink().then((link) => {
      if (link && link.url) {
        navigateFromBackground(link.url);
      }
    });
  }, []);
};

useDynamicLink.test.ts

import { renderHook, act } from '@testing-library/react-hooks';

import { navigateFromBackground } from '../deeplink';

import { getInitialLink } from './dynamicLinkService';
import { useDynamicLink } from './useDynamicLink';

jest.mock('../deeplink', () => ({
  navigateFromBackground: jest.fn(),
}));

jest.mock('./dynamicLinkService', () => ({
  getInitialLink: jest.fn(),
}));

describe('The useDynamicLink', () => {
  it('should not navigate when link in empty', async () => {
    const getInitialLinkMock = getInitialLink as jest.Mock;

    getInitialLinkMock.mockResolvedValue(null);

    await act(async () => {
      renderHook(() => useDynamicLink());
    });

    expect(navigateFromBackground).not.toHaveBeenCalled();
  });

  it('should navigate when link is exist', async () => {
    const getInitialLinkMock = getInitialLink as jest.Mock;

    getInitialLinkMock.mockResolvedValue({ url: 'www.google.com' });

    await act(async () => {
      renderHook(() => useDynamicLink());
    });

    expect(navigateFromBackground).toHaveBeenCalledWith('www.google.com');
  });
});

Your jest.spyOn needs some work.

Jest.spyOn is different than mocks in that it cleans up its mock in the scope you are inside (and that it's not really a mock until you say explicitly call mockImplentation ect. on it thus it's a 'spy.') Since you want to constantly change your mocks you should be using spyOn() and mocking the implementation in each test to reduce boilerplate from clearing the mocks each time. Both can work just fine though but I would work on attempt 3.

First, remove the mock of dynamic links since we are going to spy on each specific test instead and mock the implementation there.

Second, because you are calling on an exported function directly you have to import and spy on the function like this.

import * as dynamicLinks from '@react-native-firebase/dynamic-links';

const dynamicLinkSpy = jest.spyOn(dynamicLinks, 'dynamicLinks').mockImplentation( ... )

dynamicLinks is now the exported file jest spys on and the function it looks for is dynamicLinks(), which is what the production code is calling.

Another error is from adding .prototype. You should look at how the production code is calling it, that's how the tests should be mocking it. Also for this, you replace implementation on dynamicLinks, you have to create the return value that will work downward from the nested functions being called on that object. Also, since you're using .then() your production code expects a Promise to be resolved in the function. Like so;

const dynamicLinkSpy = jest
  .spyOn(dynamicLinks, 'dynamicLinks')
  .mockImplementation(()=>{ return {getInitialLink: ()=> Promise.resolve('test')}} );

Now, you can play with different return values and expect different results as usual. Also, remember you should test whether or not it's being called on. Like below:

expect(dynamicLinkSpy).toHaveBeenCalled();