[英]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 提供了多種模擬依賴項的方法,但是我無法使其工作。
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,因為它已被包裝。
然后我決定使用 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');
});
});
最后一次嘗試是使用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.