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

心不动则不痛 提交于 2019-12-11 12:47:35

问题


I'm building a React Native app with TypeScript. I'm using react-native-firebase for my notifications. I'm furthermore using Jest and Enzyme for my unit tests.

I have the following wrapper function to check the users permissions:

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);
        }
      });
  });

Now I want to test if the function gets called.

Here is the test I wrote:

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);
  });
});

This throws the error:

 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`.

So it seems to me that modules that use native code can't simply by auto-mocked.

So I tried to manually mock it. Inside a folder __mocks__ that's within my root project adjacent to node_modules I created a file called react-native-firebase.ts, which looks like this:

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

export default firebase;

But this code also fails, because firebase.messaging is allegedly undefined.

How would one test this stuff? 😣

EDIT: Wow, mocking seems to be completely broken in RN 0.57.x :(


回答1:


Here is a way you can test code that depends on react-native-firebase

Create a manual mock just by adding an empty file in __mocks__/react-native-firebase.js, mocks folder should be at the same level as node_modules as explained in jest docs specifically Mocking Node modules section.

With this manual mock you avoid the error

    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`.

Then you don't need jest.mock("react-native-firebase"); also you don't need to test expect(firebase.messaging().hasPermission).toHaveBeenCalledTimes(1);

What you can do instead is isolate the code that depends on react-native-firebase on small functions and then spy on those adding known results.

For example, this verifyPhone function that depends on 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;

To test code that depends on verifyPhoneNumber function you spy it and replace its implementation like this.

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

I hope it helps!



来源:https://stackoverflow.com/questions/52815957/how-to-correctly-mock-a-react-native-module-that-utilizes-native-code

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!