簡體   English   中英

Mocking 默認導出方法 class 在 Typescript

[英]Mocking method on default export class in Jest in Typescript

語境

我想測試一個自定義鈎子,這取決於@react-native-firebase/dynamic-links 我們將@testing-library用於 react-native 及其實用函數來測試鈎子( @testing-library/react-hooks )。

這是我想測試的鈎子(這是一個簡化的例子):

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);
        }
      });
  }, []);
};

我希望getInitialLink調用在每個單獨的測試中返回一些東西。 我已經能夠用jest.mock(...)模擬getInitialLink ,但是這會模擬所有測試。 我認為問題在於我想模擬的方法是 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');
  });
});

嘗試

所以這可行,但我無法更改每個測試的返回值。 Jest 提供了多種模擬依賴項的方法,但是我無法使其工作。

jest.MockedClass

Firebase 默認導出一個 class,但 class 本身是包裝好的。

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

根據文檔,您需要像下面描述的那樣模擬它。

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

但是,它會引發以下錯誤:

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

實際上,它不會將其識別為 class,因為它已被包裝。


jest.MockedFunction

然后我決定使用 function 來模擬它(而不是使用箭頭函數)。 通過這種方法,我能夠走得更遠,但是通過這種方法,我需要提供所有屬性。 我嘗試了一段時間,但在添加 X 數量的屬性后我放棄了(見下面的代碼片段)。 因此,如果這是通往 go 的方式,我想知道如何自動模擬大部分內容。

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

最后一次嘗試是使用spyOn ,這在這種情況下似乎很合適。 因為它只會模擬特定的函數,但是當我嘗試運行測試時這會引發運行時錯誤。

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 automock
jest.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');
  });
});

錯誤:

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

所以總而言之,我完全不知道如何模擬getInitialLink方法。 如果有人可以提供任何建議或提示,將不勝感激!


編輯1:

根據@user275564 的建議,我嘗試了以下方法:

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

不幸的是,由於以下錯誤,typescript 無法編譯:

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'.

我只能在 object 上提出 static 屬性,它們是: 在此處輸入圖像描述

這就是為什么我選擇了這個答案中建議的dynamicLinks.prototype

你的 jest.spyOn 需要一些工作。

Jest.spyOn 與模擬不同,因為它會在您所在的 scope 中清理其模擬(並且它不是真正的模擬,直到您明確調用 mockImplentation 等,因此它是一個“間諜”。)不斷更改您的模擬,您應該在每個測試中使用 spyOn() 和 mocking 實現,以減少每次清除模擬的樣板。 兩者都可以正常工作,但我會嘗試 3。

首先,刪除動態鏈接的模擬,因為我們將監視每個特定的測試並模擬那里的實現。

其次,因為您要直接調用導出的 function,所以您必須像這樣導入和監視 function。

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

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

dynamicLinks 現在是導出的文件 jest spys 和它尋找的 function 是 dynamicLinks(),這是生產代碼調用的。

另一個錯誤來自添加.prototype。 你應該看看生產代碼是怎么調用它的,那測試應該是mocking吧。 同樣為此,您替換了 dynamicLinks 上的實現,您必須創建將從 object 上調用的嵌套函數向下工作的返回值。 此外,由於您使用的是.then(),因此您的生產代碼需要在 function 中解析 Promise。 像這樣;

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

現在,您可以像往常一樣使用不同的返回值並期待不同的結果。 另外,請記住您應該測試它是否被調用。 如下所示:

expect(dynamicLinkSpy).toHaveBeenCalled();

我更喜歡使用動態鏈接(或其他 firebase 函數)創建服務。 很容易嘲笑。

動態鏈接服務.ts

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

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

使用DynamicLink.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);
      }
    });
  }, []);
};

使用DynamicLink.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');
  });
});

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM