繁体   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