How to correctly mock a React Native module that u

2019-07-23 18:21发布

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?

1条回答
该账号已被封号
2楼-- · 2019-07-23 18:52

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!

查看更多
登录 后发表回答