如何正确模拟利用本地代码的React Native模块?

时间:2018-10-15 11:39:27

标签: typescript unit-testing react-native jestjs react-native-firebase

我正在用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`.

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

所以我尝试手动模拟它。在我的根项目中与__mocks__相邻的文件夹node_modules中,我创建了一个名为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:(

1 个答案:

答案 0 :(得分:1)

这是一种测试取决于react-native-firebase的代码的方法

仅通过在__mocks__/react-native-firebase.js中添加一个空文件来创建手动模拟, mocks 文件夹应与node_modules处于同一级别,如in jest docs所述< strong>模拟节点模块部分。

借助此手动模拟功能,您可以避免错误

    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的代码,然后监视那些添加已知结果的代码。

例如,此verifyPhone函数依赖于react-native-firebase。

// ./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();
    });
  });

希望对您有帮助!