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

Posted

技术标签:

【中文标题】如何正确模拟使用本机代码的 React Native 模块?【英文标题】:How to correctly mock a React Native module that utilizes native code? 【发布时间】:2019-03-19 20:03:09 【问题描述】:

我正在使用 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 :(

【问题讨论】:

【参考方案1】:

这是一种测试依赖于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 的代码隔离在小函数上,然后监视那些添加已知结果的代码。

例如,这个 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();
    );
  );

希望对你有帮助!

【讨论】:

以上是关于如何正确模拟使用本机代码的 React Native 模块?的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 Jest 在本机反应中模拟“react-native-config”库的配置数据?

使用打字稿配置本机反应时出错

在React本机页面之间传递数据

如何在本机反应中模拟上下文消费者反应元素

未找到 -lBranch-SDK 的本机 xcode 库

React 本机开发服务器返回响应错误代码:500