CoolSteve
CoolSteve

Reputation: 67

Send auth context to firebase callable function in unittest

I have been working on a firebase project in which I created a cloud function that creates documents in firestore. This is the function -

export const createExpenseCategory = functions
  .region("europe-west1")
  .https.onCall(async (data, context) => { // data is a string
    if (!context.auth?.uid) { // check that requesting user is authenticated
      throw new functions.https.HttpsError(
        "unauthenticated",
        "Not Authenticated"
      );
    }

    const res = await admin
      .firestore()
      .collection("/categories/")
      .where("uid", "==", context.auth.uid)
      .get();

    const categoryExists = res.docs.find((doc) => doc.data().name === data); // check that there are not duplicates.
  //  doc looks like this -
  //  {
  //    "name": "Food",
  //    "uid": "some_long_uid"
  //  }

    if (categoryExists) {
      throw new functions.https.HttpsError(
        "already-exists",
        `Category ${data} already exists`
      );
    }

    return admin
      .firestore()
      .collection("/categories/")
      .add({ name: data, uid: context.auth.uid });
  });

As you can see, at the beginning of the function I check whether the user that sent the request is authenticated with the context parameter. Everything works fine when I play around with it in my web app, but I have been trying to figure out a way to create a unittest for this function. My problem is that I can't really figure out how to create an authenticated request to make sure that my function doesn't fail every time. I tried to look online for any documentation but couldn't seem to find any.

Thanks in advance!

Upvotes: 2

Views: 535

Answers (1)

ErnestoC
ErnestoC

Reputation: 2904

You can unit test your functions using the firebase-functions-test SDK. The guide mentions you can mock the data within the eventContext or context parameter passed to your function. This works for mocking the uid field of the auth object:

// Left out authType as it's only for RTDB
wrapped(data, {
  auth: {
    uid: 'jckS2Q0'
  }
});

The guide uses mocha for testing, but you can use other testing frameworks. I made a simple test to see if it would work and I could send the mock uid to the function, which worked as expected:

index.js

exports.authTest = functions.https.onCall( async (data, context) => {
    if(!context.auth.uid){
        throw new functions.https.HttpsError('unauthenticated', 'Missing Authentication');
    }

    const q = await admin.firestore().collection('users').where('uid', '==', context.auth.uid).get();
    const userDoc = q.docs.find(doc => doc.data().uid == context.auth.uid);

    return admin.firestore().collection('users').doc(userDoc.id).update({name: data.name});
});

index.test.js

const test = require('firebase-functions-test')({
    projectId: PROJECT_ID
}, SERVICE_ACCTKEY); //Path to service account file
const admin = require('firebase-admin');

describe('Cloud Functions Test', () => {
    let myFunction;
    before(() => {
        myFunction = require('../index.js');
    });

    describe('AuthTest', () => {
        it('Should update user name in UID document', () => {
            const wrapped = test.wrap(myFunction.authTest);

            const data = {
                name: 'FooBar'
            }
            const context = {
                auth: {
                    uid: "jckS2Q0" //Mocked uid value
                }
            }

            return wrapped(data, context).then(async () => {
                //Asserts that the document is updated with expected value, fetches it after update
                const q = await admin.firestore().collection('users').where('uid', '==', context.auth.uid).get();
                const userDoc = q.docs.find(doc => doc.data().uid == context.auth.uid);
                assert.equal(userDoc.data().name, 'FooBar');
            });
        });
    });
});

Let me know if this was useful.

Upvotes: 2

Related Questions