Quantcast
Channel: Active questions tagged react-native+typescript - Stack Overflow
Viewing all articles
Browse latest Browse all 6287

Mocking method on default export class in Jest in Typescript

$
0
0

Context

I would like to test a custom hook, that depends on @react-native-firebase/dynamic-links. We are using @testing-library for react-native and its utility functions to test hooks (@testing-library/react-hooks).

This is the hook I would like to test (this is a simplified example):

import { useEffect } from 'react';import dynamicLinks from '@react-native-firebase/dynamic-links';import { navigateFromBackground } from '../deeplink';// Handles dynamic link when app is loaded from closed state.export const useDynamicLink = (): void => {  useEffect(() => {    void dynamicLinks()      .getInitialLink()      .then((link) => {        if (link && link.url) {          navigateFromBackground(link.url);        }      });  }, []);};

I would like the getInitialLink call to return something in each separate test. I have been able to mock getInitialLink with jest.mock(...), however this mocks it for all tests. I think the trouble is that the method I would like to mock, is a method on a class.

import { useDynamicLink } from './useDynamicLink';import { renderHook, act } from '@testing-library/react-hooks';import { navigateFromBackground } from '../deeplink';jest.mock('../deeplink');// IMPORTANT: You cannot mock constructors with arrow functions. New cannot be// called on an arrow function.jest.mock('@react-native-firebase/dynamic-links', () => {  return function () {    return {      getInitialLink: async () => ({        url: 'fake-link',      }),    };  };});describe('tryParseDynamicLink', () => {  it('should return null if url is empty', async () => {    // IMPORTANT: act wrapper is needed so that all events are handled before    // state is inspected by the test.    await act(async () => {      renderHook(() => useDynamicLink());    });    expect(navigateFromBackground).toHaveBeenCalledWith('fake-link');  });});

Attempts

So this works, but I am not able to change the return value for each test. Jest offers a wide variety of ways to mock dependencies, however I was not able to make it work.

jest.MockedClass

Firebase exports by default a class, but the class itself is wrapped.

declare const defaultExport: ReactNativeFirebase.FirebaseModuleWithStatics<  FirebaseDynamicLinksTypes.Module,  FirebaseDynamicLinksTypes.Statics>;

According to the documentation, you would need to mock it like described below.

import dynamicLinks from '@react-native-firebase/dynamic-links';const dynamicLinksMock = dynamicLinks as jest.MockedClass<typeof dynamicLinks>;

It however throws the following error:

Type 'FirebaseModuleWithStatics<Module, Statics>' does not satisfy the constraint 'Constructable'.  Type 'FirebaseModuleWithStatics<Module, Statics>' provides no match for the signature 'new (...args: any[]): any'.

Effectively it does not recognise it as a class since it is wrapped.


jest.MockedFunction

I decided to then mock it by using a function (and not using arrow functions). With this approach I was able to get a lot further, however with this approach I need to provide all properties. I attempted this for a while, but I gave up after adding X amount of properties (see code snippet below). So if this is the way to go, I would like to know how to automock most of this.

import { useDynamicLink } from './useDynamicLink';import { renderHook, act } from '@testing-library/react-hooks';import { navigateFromBackground } from '../deeplink';import dynamicLinks from '@react-native-firebase/dynamic-links';const dynamicLinksMock = dynamicLinks as jest.MockedFunction<  typeof dynamicLinks>;jest.mock('../deeplink');describe('tryParseDynamicLink', () => {  it('should return null if url is empty', async () => {    // eslint-disable-next-line prefer-arrow-callback    dynamicLinksMock.mockImplementationOnce(function () {      return {        buildLink: jest.fn(),        buildShortLink: jest.fn(),        app: {          options: {            appId: 'fake-app-id',            projectId: 'fake-project-id',          },          delete: jest.fn(),          utils: jest.fn(),          analytics: jest.fn(),          name: 'fake-name',          crashlytics: jest.fn(),          dynamicLinks: jest.fn(),        },        onLink: jest.fn(),        resolveLink: jest.fn(),        native: jest.fn(),        emitter: jest.fn(),        getInitialLink: async () => ({          minimumAppVersion: '123',          utmParameters: { 'fake-param': 'fake-value' },          url: 'fake-link',        }),      };    });    await act(async () => {      renderHook(() => useDynamicLink());    });    expect(navigateFromBackground).toHaveBeenCalledWith('fake-link');  });});

jest.spyOn

The last attempt was to use spyOn which seems fitting in this case. Since it will mock only specific functions, however this throws a runtime error when I try to run the tests.

import { useDynamicLink } from './useDynamicLink';import { renderHook, act } from '@testing-library/react-hooks';import { navigateFromBackground } from '../deeplink';import dynamicLinks from '@react-native-firebase/dynamic-links';jest.mock('../deeplink');// Ensure automockjest.mock('@react-native-firebase/dynamic-links');describe('tryParseDynamicLink', () => {  it('should return null if url is empty', async () => {    jest      .spyOn(dynamicLinks.prototype, 'getInitialLink')      .mockImplementationOnce(async () => 'test');    await act(async () => {      renderHook(() => useDynamicLink());    });    expect(navigateFromBackground).toHaveBeenCalledWith('fake-link');  });});

Error:

Cannot spy the getInitialLink property because it is not a function; undefined given instead

So all in all I am at a complete loss on how to mock the getInitialLink method. If anyone could provide any advice or tips it would be greatly appreciated!


Edit 1:

Based on the advice of @user275564 I tried the following:

jest.spyOn(dynamicLinks, 'dynamicLinks').mockImplementation(() => {   return { getInitialLink: () => Promise.resolve('fake-link') };});

Unfortunately typescript does not compile because of the following error:

No overload matches this call.  Overload 1 of 4, '(object: FirebaseModuleWithStatics<Module, Statics>, method: never): SpyInstance<never, never>', gave the following error.    Argument of type 'string' is not assignable to parameter of type 'never'.  Overload 2 of 4, '(object: FirebaseModuleWithStatics<Module, Statics>, method: never): SpyInstance<never, never>', gave the following error.    Argument of type 'string' is not assignable to parameter of type 'never'.

I am only able to put forth the static properties on the object there which are:enter image description here

This is why I went for the dynamicLinks.prototype which was suggested in this answer.


Viewing all articles
Browse latest Browse all 6287

Trending Articles



<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>