How to mock implementation for FCM admin.messaging().sendToDevice() in Firebase Cloud Functions Tests

3.3k Views Asked by At

I'm unable to mock the Firebase Messaging functions in Typescript for Firebase Cloud Functions. I'm using firebase-functions-test Companion SDK to build Online Tests as mentioned in the docs. I'm using jest mocks to mock the admin.messaging().sendToDevice function.

const fcmMock = jest.spyOn(admin.messaging(),'sendToDevice');

fcmMock.mockImplementation(async (registrationToken: string | string[], payload: admin.messaging.MessagingPayload, options?: admin.messaging.MessagingOptions)=>{
      console.log('FCM Mock called with message: '+payload.notification+'\nToken ids:'+registrationToken);
      return new Promise<admin.messaging.MessagingDevicesResponse>(undefined);
}); 

Running this gets the following error because the return type object MessagingDevicesResponse>(undefined) requires a complex argument:

Argument of type 'undefined' is not assignable to parameter of type '(resolve: (value?: MessagingDevicesResponse | PromiseLike | undefined) => void, reject: (reason?: any) => void) => void'.

The code I'm going to test:


export async function cleanupToken(response: admin.messaging.MessagingDevicesResponse, userDataSnapshot:FirebaseFirestore.DocumentSnapshot) {
    // For each notification we check if there was an error.
    if(response.results.length>0){
        const error = response.results[0].error;
        if (error) {
            // Cleanup the tokens who are not registered anymore.
            // Error Codes: https://firebase.google.com/docs/cloud-messaging/send-message#admin_sdk_error_reference
            if (error.code === 'messaging/invalid-registration-token' ||
                error.code === 'messaging/registration-token-not-registered') {
                // Some Logic here to cleanup the token and mark the user
                }
            }
        }
    }
}
1

There are 1 best solutions below

1
Haider Malik On

looking at your code there are a few things missing/incorrect:

  1. change jest.spyOn(admin.messaging(),'sendToDevice'); to jest.spyOn(admin,'sendToDevice');
  2. pass in mock objects, in this case response and snapshot

    // A fake response object, with a send to test the response
    const res = {
          send: (response) => {
              expect(response).toBe("Hello from Firebase!");
              done();
              }
          };
    
    //how to create a datasnapshot. Takes in 2 parameters, the object and the reference path
    const userDataSnapshot = test.database.makeDataSnapshot(Object,'ref/to-the/path');
    
  3. you dont return a promise in a jest test case, instead you use the jest test method (in this case expect).

  4. Normally the jest test (expect in this case) can be done in the function but we are doing it in the response since that is the callback hence it takes place right at the end.

You test case should look something like this:

describe('testing jest functions',() =>{
  let index,adminStub;


  //setup test enviroment
  beforeAll(() =>{
      adminStub = jest.spyOn(admin, "initializeApp");
      index = require('./index');

      return;
  });

  //tear down
  afterAll(() =>{
    adminStub.mockRestore();
    testEnv.cleanup();
  });

  it('test cleanupToken function', (done) =>{

        const res = {
              send: (response) => {
                    //assuming you use response.send in your actual code you can test it
                  expect(response).toBe("Hello from Firebase!");
                  done();
                  }
              };

        const userDataSnapshot = test.database.makeDataSnapshot(Object,'ref/to-the/path');

      index.cleanupToken(res,userDataSnapshot);
  });