【发布时间】:2022-01-23 02:43:30
【问题描述】:
上下文
我想测试一个自定义钩子,它依赖于@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,但是这会在所有测试中模拟它。我认为问题在于我想模拟的方法是类上的方法。
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 默认导出一个类,但该类本身是封装的。
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'.
实际上它不会将其识别为一个类,因为它已被包装。
然后我决定使用函数来模拟它(而不是使用箭头函数)。通过这种方法,我能够走得更远,但是通过这种方法,我需要提供所有属性。我尝试了一段时间,但在添加 X 数量的属性后我放弃了(参见下面的代码 sn-p)。所以如果这是要走的路,我想知道如何自动模拟大部分内容。
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'.
这就是我选择dynamicLinks.prototype 的原因,这在answer 中提出了建议。
【问题讨论】:
标签: typescript react-native jestjs mocking react-native-firebase