React-native-firebase: Document how to mock react-native-firebase for jest

Created on 15 Feb 2019  ยท  37Comments  ยท  Source: invertase/react-native-firebase

Documentation Feedback

There is no documentation (that I found) on how to make jest tests run when the code being tested involves react-native-firebase.
_(It also could be a bug in either project but since the most relevant issues I found were closed; I assume it's not a bug but simply an underdocumented config that is the problem.)_

After upgrading to react-native 0.57.8 and upgrading react-native-firebase the tests no longer run.
I've fixed all issues except one test that keeps failing with this error:

 FAIL  src/sagas/__tests__/initFlow-test.js
  โ— 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 `pod install`.

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



      at new Firebase (node_modules/react-native-firebase/dist/modules/core/firebase.js:61:13)
      at Object.<anonymous> (node_modules/react-native-firebase/dist/modules/core/firebase.js:108:19)
      at Object.<anonymous> (node_modules/react-native-firebase/dist/index.js:9:41)
      at Object.<anonymous> (src/utils/firebase/index.js:34:51)

I'm on a Linux machine so running pod install is not an option for me, so it may in fact not be correctly installed for iOS. But surely while running tests it should not use native code for iOS anyway? So I'm sure RNF should be mocked somehow when running tests.

(I assume it must have been "automocked" before i upgraded stuff since there was a setting automock: true in the jest configuration, but this option now causes every test to fail instead so I commented that out.)
I've not located anything on how to fix this in the docs (maybe it's there but I haven't found it.).

I found this issue https://github.com/invertase/react-native-firebase/issues/162 but all the wildly different and verbose suggestions there either didn't do anything at all or I didn't understand how to implement it so it didn't work.

I also found this article:
https://medium.com/@rui.fernandes/react-native-jest-react-native-firebase-b5245d8ddd1
And this suggestion did something because at least the error message changed to:

 FAIL  src/sagas/__tests__/initFlow-test.js
  โ— Test suite failed to run

    TypeError: _reactNativeFirebase.default.app is not a function



      at Object.<anonymous> (src/utils/firebase/index.js:36:45)
      at Object.<anonymous> (src/sagas/firebaseFlow.js:516:40)
      at Object.<anonymous> (src/sagas/index.js:132:44)

This fix is also pretty verbose and it's almost a year old so it might be outdated.

But surely there should be a simple premade mock implementation or a setting somewhere to make jest just ignore all calls to firebase that is not very well documented or something? I'm not supposed to manually write massive mock implementations of RNF just to run the tests that prevously ran without any mocks at all, now am I?.

(I'm pretty sure the tests aren't actually testing RNF, since as far as I know it's only used for logging in this project. But I'm not sure because I just inherited this project and everyone who worked on this app have since quit.)

~I'm not sure whether this issue belongs here or on https://github.com/invertase/react-native-firebase/issues/ since it's either a bug or just incomplete documentation relating to these two packages interacting. So I've posted this issue there as well.~ _They closed it immediately, so apparently they think it belongs here._

General Docs Stale Testing

Most helpful comment

For now, I've got this going on and it works for me (you may need to mock more or fewer modules or more or fewer functions, I will certainly be adding more for unit tests, for instance). Versions are RN0.59.3 / RN-Firebase 5.x.x / Jest 24.7.1 on ubuntu 18.10 and mac 10.14.1 (I put all those versions in because this stuff moves quickly...)

The line from jest.config.js (using the new setup files parameter):

  setupFilesAfterEnv: ['./__mocks__/mockFirebase.js'],

And in __mocks__/mockFirebase.js:

/* eslint-disable no-undef */
jest.mock('react-native-firebase', () => ({
  messaging: jest.fn(() => ({
    hasPermission: jest.fn(() => Promise.resolve(true)),
    subscribeToTopic: jest.fn(),
    unsubscribeFromTopic: jest.fn(),
    requestPermission: jest.fn(() => Promise.resolve(true)),
    getToken: jest.fn(() => Promise.resolve('myMockToken')),
  })),
  notifications: jest.fn(() => ({
    onNotification: jest.fn(),
    onNotificationDisplayed: jest.fn(),
  })),
  analytics: jest.fn(() => ({
    logEvent: jest.fn(),
  })),
}));

As was discussed above doing "real" testing (e2e etc) is preferable in some situations but this is unit tests on a commit hook, I'm not doing e2e for that so I think it's a valid approach?

All 37 comments

Hello - this is something we'll get documented with the v6 release. It may be worth taking a look at https://github.com/invertase/react-native-firebase/issues/162 for some additional information.

Yes, I found that issue, but I could not understand or get the suggestions there to work; and none of those seems like the definite Correct Solution anyway...

I also stuck while mocking this module for jest.
I try to create mock file, something like

const firebase = {
  messaging: jest.fn(() => ({
    hasPermission: jest.fn(() => Promise.resolve(true)),
    subscribeToTopic: jest.fn(),
    unsubscribeFromTopic: jest.fn(),
    requestPermission: jest.fn(() => Promise.resolve(true)),
    getToken: jest.fn(() => Promise.resolve('myMockToken')),
  })),
  notifications: jest.fn(() => ({
    onNotification: jest.fn(),
    onNotificationDisplayed: jest.fn(),
  })),
},

export default firebase;

But I have a several cases in code while calling notifications
for example
firebase.notifications().android.createChannel(channel);
firebase.notifications.Android.Importance.Max
and

new firebase.notifications.Notification()
    .setNotificationId(id)
    .setTitle(title)
    .setSound('default')
    .setBody(body);

And I don`t know how to create mock file for this all cases.

Hi Guys, I've added this to our project board as a TODO for docs: https://github.com/orgs/invertase/projects/2?fullscreen=true

I'll speak with @Salakar and see what we can do here to improve things on our end.

Guys maybe you have a solution for mocking for now ? I cant write my tests.
I already try to mock jest.mock('react-native-firebase'); but it still not works.

TypeError: _reactNativeFirebase.default.notifications is not a function

      45 |
      46 |   firebase
    > 47 |     .notifications()
         |   ^

Personally, I'd suggest using Detox with Jest, you won't need to mock react-native-firebase at all then if you setup e2e tests - this is what we do for all our tests for the library.

If you're looking for a full example project that has e2e testing setup with Detox then I'd recommend checking out https://github.com/ueno-llc/react-native-starter


We can maybe look at adding official mocks but that adds another layer of additional effort and complexity required on top of all the extensive work we're already doing. For example with Database, it wouldn't be as simple as adding a jest.fn() to every method as we couldn't make any assumptions on what your data looks like, same goes for Firestore & Storage etc.

v6.0.0 (on master) breaks each Firebase service up into its own package, after v6.0.0 is released it'd be a better time to look at this - as mocks can be added individually to each package without needing to do all of them at once; so adding mocks for all the smaller ones is definitely a possibility.

Thanks. I know about detox, but it using for another kind of testing. I planed to try it in next few days.
I need mock firebase module for making unit testing.

Hello ๐Ÿ‘‹, this issue has been automatically marked as stale because it has not had activity for quite some time. Has the issue been fixed, or does it still require the community's attention? This issue may be closed if no further activity occurs. Thank you for your contributions.

@kholiavko-roman have you find any solution for mocking both function and object with the same name? I kinda have a similar issue with firebase.auth

@Muneefm No, I didn't slove that problem correctly.
I create some fix like this where I call local notification:
if (process.env.NODE_ENV !== 'test') { ... }
Simply don't run this code when jest running.

@kholiavko-roman oh. nice. I just took one of them and placed in another helper module as a function and mocked the entire function at once. as a workaround.
hoping to see a real solution for this soon.

cheers

For now, I've got this going on and it works for me (you may need to mock more or fewer modules or more or fewer functions, I will certainly be adding more for unit tests, for instance). Versions are RN0.59.3 / RN-Firebase 5.x.x / Jest 24.7.1 on ubuntu 18.10 and mac 10.14.1 (I put all those versions in because this stuff moves quickly...)

The line from jest.config.js (using the new setup files parameter):

  setupFilesAfterEnv: ['./__mocks__/mockFirebase.js'],

And in __mocks__/mockFirebase.js:

/* eslint-disable no-undef */
jest.mock('react-native-firebase', () => ({
  messaging: jest.fn(() => ({
    hasPermission: jest.fn(() => Promise.resolve(true)),
    subscribeToTopic: jest.fn(),
    unsubscribeFromTopic: jest.fn(),
    requestPermission: jest.fn(() => Promise.resolve(true)),
    getToken: jest.fn(() => Promise.resolve('myMockToken')),
  })),
  notifications: jest.fn(() => ({
    onNotification: jest.fn(),
    onNotificationDisplayed: jest.fn(),
  })),
  analytics: jest.fn(() => ({
    logEvent: jest.fn(),
  })),
}));

As was discussed above doing "real" testing (e2e etc) is preferable in some situations but this is unit tests on a commit hook, I'm not doing e2e for that so I think it's a valid approach?

This saved my day!Top!

Hello ๐Ÿ‘‹, to help manage issues we automatically close stale issues.
This issue has been automatically marked as stale because it has not had activity for quite some time. Has this issue been fixed, or does it still require the community's attention?

This issue will be closed in 15 days if no further activity occurs.
Thank you for your contributions.

There's any solution on some hack? I can't mock notifications (object) and notifications (function) in the same mock...

This worked for me. @javierbustamante hopefully it helps you, too:

const firebase = {
  messaging: jest.fn(() => ({
    hasPermission: jest.fn(() => Promise.resolve(true)),
    subscribeToTopic: jest.fn(),
    unsubscribeFromTopic: jest.fn(),
    requestPermission: jest.fn(() => Promise.resolve(true)),
    getToken: jest.fn(() => Promise.resolve('myMockToken')),
    onTokenRefresh: jest.fn(() => Promise.resolve('myMockToken'))
  })),
  notifications: jest.fn(() => ({
    onNotification: jest.fn(),
    onNotificationDisplayed: jest.fn(),
    android: {
      createChannel: jest.fn()
    }
  }))
};

firebase.notifications.Android = {
  Channel: jest.fn(() => ({
    setDescription: jest.fn()
  })),
  Importance: {
    Max: {}
  }
};

export default firebase;

Hello ๐Ÿ‘‹, to help manage issues we automatically close stale issues.
This issue has been automatically marked as stale because it has not had activity for quite some time. Has this issue been fixed, or does it still require the community's attention?

This issue will be closed in 15 days if no further activity occurs.
Thank you for your contributions.

Based on the suggestion to use do e2e tests without mocking react-native-firebase, can anyone give tips on how to avoid changing the prod database?

In the detox documentation they recommend using *.e2e.js files to change the url endpoints. I'm thinking about creating a test node on firebase with test data and changing the references in the code, but I believe that using an entirely different database would be better, but that would require changing the google-services.json file, and that's not supported by detox.

Does anyone have a solution for that?

I have 4 different fully separate Firebase projects. (dev, test, staging, prod). I use gradle flavors for android and separate plists / targets for iOS.

part of my android/app/build.gradle

    signingConfigs {
        devSigning {
            storeFile rootProject.file("keystores/dev.keystore")
            keyAlias ...etcetcetc...
        }
        qaSigning {
            storeFile rootProject.file("keystores/qa.keystore")
            keyAlias ...etcetcetc...
        }
        stagingSigning {
            storeFile rootProject.file("keystores/staging.keystore")
            keyAlias ...etcetcetc...
        }
        prodSigning {
            storeFile rootProject.file("keystores/prod.keystore")
            keyAlias ...etcetcetc...
        }
    }

    flavorDimensions "release_streams"
    productFlavors {
        dev {
            applicationIdSuffix ".dev"
            versionNameSuffix applicationIdSuffix
        }
        qa {
            applicationIdSuffix ".test"
            versionNameSuffix applicationIdSuffix
        }
        staging {
            applicationIdSuffix ".staging"
            versionNameSuffix applicationIdSuffix
        }
        prod {

        }
    }

ios looks like this approach https://reactnative-tricks.com/using-multiple-firebase-environments-in-ios/

in my experience mocking is great but you still need a real backend for lots of testing (if only testing data migrations etc), and I don't want my dev testing to pollute my analytics.

In combo with fastlane and a good script for publishing android stuff it's automated for compile + release and works really well for me

Hello ๐Ÿ‘‹, to help manage issues we automatically close stale issues.
This issue has been automatically marked as stale because it has not had activity for quite some time. Has this issue been fixed, or does it still require the community's attention?

This issue will be closed in 15 days if no further activity occurs.
Thank you for your contributions.

Closing this issue after a prolonged period of inactivity. If this is still present in the latest release, please feel free to create a new issue with up-to-date information.

hi @Ehesp - i was looking for the card on the board you mentioned for this, but was unable to locate it
https://github.com/orgs/invertase/projects/2?fullscreen=true&card_filter_query=jest

Given the GitHub Issue is closed / marked as done, I had been expecting to find this in the docs - but I'm not finding it there either. Would you point to where this is documented for v6?

@mikehardy
Any suggestion how to mock the @react-native-firebase new version?

Sorry @claudioviola I haven't moved to RNFBv6 yet so I'm not sure.

@claudioviola

Something like this:

jest.mock('@react-native-firebase/analytics', () => {
  return () => ({
    logEvent: jest.fn(),
    setUserProperties: jest.fn(),
    setUserId: jest.fn(),
    setCurrentScreen: jest.fn(),
  })
});

Hello - if anyone wants to write a quick guide for this on v6 please let me know!

Based on the suggestion to use do e2e tests without mocking react-native-firebase, can anyone give tips on how to avoid changing the prod database?

In the detox documentation they recommend using *.e2e.js files to change the url endpoints. I'm thinking about creating a test node on firebase with test data and changing the references in the code, but I believe that using an entirely different database would be better, but that would require changing the google-services.json file, and that's not supported by detox.

Does anyone have a solution for that?

Were you able to find a way to use a different firebase database for testing?

@awalmubarak
Managed it with fastlane.

iOS Example
package json
"ios:development": "npm run ios_switch_env:development && react-native run-ios --simulator=\"iPhone Xs\"",

Fastfile iOS folder

lane :switch_env do |options|

    # Load content of dotenv file based on param `env`
    Dotenv.load("../../.env.#{options[:env]}")
    .....
      # Copy firebase plist in correct ios folder
    sh("cp -R \"../../env/#{options[:env]}/ios/GoogleService-Info.plist\" \"../#{ENV["XCODE_PROJECT_NAME"]}/Firebase/GoogleService-Info.plist\"")
   ....

When I runyarn ios:development
switch_env lane takes care to copy GoogleService-Info.plist from env folder (in the root added to git ignore) into the ios folder

That's it.

@awalmubarak
Managed it with fastlane.

iOS Example
package json
"ios:development": "npm run ios_switch_env:development && react-native run-ios --simulator="iPhone Xs"",

Fastfile iOS folder

lane :switch_env do |options|

    # Load content of dotenv file based on param `env`
    Dotenv.load("../../.env.#{options[:env]}")
    .....
      # Copy firebase plist in correct ios folder
    sh("cp -R \"../../env/#{options[:env]}/ios/GoogleService-Info.plist\" \"../#{ENV["XCODE_PROJECT_NAME"]}/Firebase/GoogleService-Info.plist\"")
   ....

When I runyarn ios:development
switch_env lane takes care to copy GoogleService-Info.plist from env folder (in the root added to git ignore) into the ios folder

That's it.

So you have two GoogleService-Info.plist files in your env folder and deciding which one to use based on the env with fastlane, or you have just one plist file in the env folder and fastlane uses that when you run in test env?

Some people have multiple plist files and copy in the one they want at build time via a filesystem copy, that can work

I have iOS "release schemes", with one folder for each scheme, with an environment-specific plist file in there, and the plist for each environment belongs only to the release scheme for that environment, so each environment is built by release scheme and gets its config

There are probably a bunch of other ways as well

@awalmubarak
I have 4 different env: dev, stage, beta, production and so I have 4 different GoogleService-Info.plist.

Fastlane takes care to replace the info.plist based on the command that I run

I had to do something a little weird to get it working for v6 (@react-native-firebase/firestore).
I was getting TypeError: (0 , _firestore.default) is not a function so I tried this:

jest.mock('@react-native-firebase/firestore', () => () => {
  return {
    collection: jest.fn(() => { doc, where, orderBy, etc. can go in here }),
  };
});

@AndroidDoctorr How did you mock the core package?

I had to do something a little weird to get it working for v6 (@react-native-firebase/firestore).
I was getting TypeError: (0 , _firestore.default) is not a function so I tried this:

jest.mock('@react-native-firebase/firestore', () => () => {
  return {
    collection: jest.fn(() => { doc, where, orderBy, etc. can go in here }),
  };
});

I am using '@react-native-firebase/messaging'

import messaging from '@react-native-firebase/messaging'
....
const authStatus = await messaging().requestPermission()
  const enabled =
    authStatus === messaging.AuthorizationStatus.AUTHORIZED ||
    authStatus === messaging.AuthorizationStatus.PROVISIONAL
...

I need to mock the AuthorizationStatus values also.

I tried the below approach but it doesn't work.

jest.mock('@react-native-firebase/messaging', () => (
  {
    __esModule: true, // this property makes it work
    default: () => ({
      requestPermission: jest.fn(),
      onTokenRefresh: jest.fn()
    }),
    AuthorizationStatus: {
      AUTHORIZED: 1,
      PROVISIONAL: 2,
      NOT_DETERMINED: 3,
      DENIED: 4
    }
  }
))

Any suggestions how I can mock constants too ?

If anyone needs only the getToken method, i managed to solve using this:

jest.mock('@react-native-firebase/messaging', () => () => {
  return {
    getToken: jest.fn(() => Promise.resolve('fd79y-tiw4t-9ygv2-4fiw4-yghqw-4t79f'))
  }
})

In __mocks__/@react-native-firebase/messaging.js and referencing it in the setupFilesAfterEnv array, in jest.config.js.

@HannanShaikYara This works for mocking out the constants:

jest.mock('@react-native-firebase/messaging', () => {
    const module = () => {
        return {
            getToken: jest.fn(() => '1234'),
        }
    }

    module.AuthorizationStatus = {
        NOT_DETERMINED: -1,
        DENIED: 0,
        AUTHORIZED: 1,
        PROVISIONAL: 2,
    }

    return module
})

Thanks! Hannan Shaik
simply just mocking, it works out for me!!
jest.mock('@react-native-firebase/messaging', () => {
const messagingModule = () => ({
getToken: jest.fn(() => Promise.resolve('myMockToken')),
setBackgroundMessageHandler: jest.fn(),
onMessage: jest.fn(),
requestPermission: jest.fn(),
onNotificationOpenedApp: jest.fn()
});
messagingModule.AuthorizationStatus = {
NOT_DETERMINED: -1,
DENIED: 0,
AUTHORIZED: 1,
PROVISIONAL: 2,
}
return messagingModule
});

Was this page helpful?
0 / 5 - 0 ratings