firebase / firebase-functions-test

MIT License
232 stars 48 forks source link

Testing onWrite cloud function does not correctly return `ref.parent` #84

Open ospfranco opened 3 years ago

ospfranco commented 3 years ago

Version info

firebase-functions-test: 0.2.3

firebase-functions: 3.12.0

firebase-admin: 9.4.1

Test case

Assume the following firebase function that triggers on a sub-field change:

functions.database.ref(`/users/{uid}/${fieldname}`).onWrite((change, context) => {
    const uid = context.params.uid
    const value = change.after.val()

    if (!change.before.exists() && !value) {
      return null
    }

    return change.after.ref.parent?.once('value').then(snapshot => {
      const user = snapshot.val()
      const email = user?.providerData?.email

      if (typeof email !== 'string') {
        return null
      }

      return createOrUpdateContact(uid, user)
    })
  })

Now to write a unit test for such function:

it('correctly updates flag at mailer', async (done) => {
    const userId = chance.guid();
    const userEmail = chance.email()
    const userAfter = {
      uid: userId,
      subscription: true,
      providerData: {
        email: userEmail
      }
    };

    const beforeSnap = functionsMock.database.makeDataSnapshot(
      {
        uid: userId,
        subscription: false,
        providerData: {
          email: userEmail
        }
      },
      `/users/${userId}`
    );

    const afterSnap = functionsMock.database.makeDataSnapshot(
      true,
      `/users/${userId}/subscription`
    );

    const wrapped = functionsMock.wrap(update_subscription_flag);

    const change = functionsMock.makeChange(beforeSnap, afterSnap);

    const res = await wrapped(change, {
      params: {
        uid: userId
      }
    });

    expect(res).toBeTruthy()

    expect(createOrUpdateContact).toHaveBeenCalledWith(userId, userAfter)

    done()
  })

Steps to reproduce

So, the exact problem occurs on this line:

return change.after.ref.parent?.once('value').then(snapshot => {

Even though change.after does correctly reference only the field that changed, when trying to get it's parent null is returned instead of the value in the beforeSnap

Expected behavior

The correct parent ref is detected or at least there is a way to set it so the real firebase behavior can be replicated

Actual behavior

The ref's parent is set to null, which diverges from real firebase behavior

sTranaeus commented 1 year ago