如何正确模拟使用本机代码的 React Native 模块?

How to correctly mock a React Native module that utilizes native code?

我正在使用 TypeScript 构建一个 React Native 应用程序。我正在为我的通知使用 react-native-firebase。我还在使用 Jest 和 Enzyme 进行单元测试。

我有以下包装函数来检查用户权限:

export const checkPermissions = (): Promise<boolean> =>
  new Promise((resolve, reject) => {
    firebase
      .messaging()
      .hasPermission()
      .then(enabled => {
        if (enabled) {
          resolve(enabled);
        } else {
          firebase
            .messaging()
            .requestPermission()
            .then(resolve)
            .catch(reject);
        }
      });
  });

现在我想测试函数是否被调用。

这是我写的测试:

import * as firebase from "react-native-firebase";
import { checkPermissions } from "./notificationHelpers";

jest.mock("react-native-firebase");

describe("checkPermissions", () => {
  beforeEach(async done => {
    jest.resetAllMocks();
    await checkPermissions();
    done();
  });

  it("should call firebase.messaging().hasPermission()", () => {
    expect(firebase.messaging().hasPermission).toHaveBeenCalledTimes(1);
  });
});

这会引发错误:

 FAIL  app/services/utils/core/notificationHelpers/notificationHelpers.test.ts
  ● Test suite failed to run

    RNFirebase core module was not found natively on iOS, ensure you have correctly included the RNFirebase pod in your projects `Podfile` and have run `podinstall`.

     See http://invertase.link/ios for the ios setup guide.

      Error: RNFirebase core module was not found natively on iOS, ensure you have correctly included the RNFirebase pod in your projects `Podfile` and haverun `pod install`.

所以在我看来,使用本机代码的模块不能简单地通过自动模拟。

所以我尝试手动模拟它。在我的根项目中与 node_modules 相邻的文件夹 __mocks__ 中,我创建了一个名为 react-native-firebase.ts 的文件,如下所示:

const firebase = {
  messaging: jest.fn(() => ({
    hasPermission: jest.fn(() => new Promise(resolve => resolve(true)))
  }))
};

export default firebase;

但是这段代码也失败了,因为据称 firebase.messaging 未定义。

如何测试这些东西?

编辑:哇,mocking seems to be completely broken in RN 0.57.x:(

这是一种测试依赖于 react-native-firebase

的代码的方法

只需在 __mocks__/react-native-firebase.js 中添加一个空文件即可创建手动模拟,mocks 文件夹应与 node_modules 处于同一级别,如 in jest docs 特别是 模拟节点模块 部分。

使用此手动模拟,您可以避免错误

    RNFirebase core module was not found natively on iOS, ensure you have correctly included the RNFirebase pod in your projects `Podfile` and have run `pod install`.

那你不需要jest.mock("react-native-firebase");你也不需要 测试 expect(firebase.messaging().hasPermission).toHaveBeenCalledTimes(1);

您可以做的是将依赖于 react-native-firebase 的代码隔离在小函数上,然后监视那些添加已知结果的代码。

比如这个依赖于react-native-firebase的verifyPhone函数

// ./lib/verifyPhoneNumber.ts
import firebase from "react-native-firebase";

const verifyPhoneNumber = (phoneNumber: string) => {
  return new Promise((resolve, reject) => {
    firebase
      .auth()
      .verifyPhoneNumber(phoneNumber)
      .on("state_changed", phoneAuthSnapshot => {
        resolve(phoneAuthSnapshot.state);
      });
  });
};

export default verifyPhoneNumber;

要测试依赖于 verifyPhoneNumber 函数的代码,您需要监视它并像这样替换它的实现。

jest.mock("react-native-firebase");

import { signInWithPhone } from "../actions";
import verifyPhoneNumberEpic from "./verifyPhoneNumberEpic";
import { ActionsObservable } from "redux-observable";
// Note "import * as" is needed to use jest.spyOn(verifyPhoneNumber, "default");
import * as verifyPhoneNumber from "./lib/verifyPhoneNumber";

describe("Epic: verifyPhoneNumberEpic", () => {
  test('On Request "[Auth] Verify Phone Number Request" executes the promise', done => {
    // Create the spy
    const verifyPhoneNumberMock = jest.spyOn(verifyPhoneNumber, "default");
    // For this test I simulate the promise resolves with CODE_SENT
    verifyPhoneNumberMock.mockImplementation(async () => "CODE_SENT");
    // the rest of the code is very specific for testing Epics (redux-observable)
    // But all you need to know is that internally my epic verifyPhoneNumberEpic
    // calls verifyPhoneNumber but the implication will be replaced with the spy

    const requestAction = signInWithPhone.request({
      phoneNumber: "+40111222333"
    });
    const action$ = ActionsObservable.of(requestAction);

    verifyPhoneNumberEpic(action$, null, null).subscribe((action: any) => {
      expect(action.payload.code).toBe("CODE_SENT");
      expect(action.type).toBe(signInWithPhone.success.toString());
      done();
    });
  });

希望对您有所帮助!