firebase / firebase-functions-test

MIT License
232 stars 48 forks source link

admin.firestore.FieldValue throws error. #96

Closed xanderfehsenfeld closed 3 years ago

xanderfehsenfeld commented 3 years ago

Version info

firebase --version returns 9.10.0

./package.json

    "firebase-admin": "^9.6.0",
    "firebase-functions-test": "^0.2.3",

./functions/package.json

 "firebase-functions": "^3.1.0",

Test case

./functions/src/activities/fieldValue.test.ts

import admin = require("firebase-admin");
import * as functionsTest from "firebase-functions-test";

const testName = "spont-app-test";

// initialize test database

process.env.GCLOUD_PROJECT = testName;
process.env.FIRESTORE_EMULATOR_HOST = "localhost:8080";
process.env.FIREBASE_AUTH_EMULATOR_HOST = "localhost:9099";
const projectConfig = { projectId: testName };

const test = functionsTest(projectConfig);

admin.initializeApp(projectConfig);
const initialData = {
  array: ["test"],
};
const before = test.firestore.makeDocumentSnapshot(
  initialData,
  "collection/doc"
);

const db = admin.firestore();

const after = test.firestore.makeDocumentSnapshot(
  {
    array: ["test"],
  },

  "collection/doc"
);

describe("testFieldValue", () => {
  let testFieldValue;
  let document: FirebaseFirestore.DocumentSnapshot;
  beforeAll(async () => {
    await db.collection("collection").doc("doc").set(initialData);

    testFieldValue = require("./fieldValue").testFieldValue;

    const wrapped = test.wrap(testFieldValue);

    await wrapped({ before, after });

    document = await db.collection("collection").doc("doc").get();
  });

  it("removes from array", () => {
    expect(document?.data()?.array).not.toContain("test");
  });

  afterAll(async () => {
    // Do cleanup tasks.
    await test.cleanup();
    await test.firestore.clearFirestoreData({ projectId: testName });
  });
});

./functions/src/activities/fieldValue.ts

import { firestore } from "firebase-admin";
import * as functions from "firebase-functions";

export const testFieldValue = functions.firestore
  .document("activities/{activity}")
  .onWrite(async (change) => {
    const data = change.after.data();

    if (data) {
      await change.after.ref.set({
        array: firestore.FieldValue.arrayRemove("test"),
      });
    }
  });

Steps to reproduce

Run the test, which calls a firebase firestore trigger function which uses firestore.FieldValue.arrayUnion

./package.json

    "test:watch": "firebase emulators:exec --only firestore,auth 'FIRESTORE_EMULATOR_HOST=\"localhost:8080\" jest --watch --detectOpenHandles  --runInBand'",

npm run test:watch

Expected behavior

The tests pass and the value 'test' is removed from the array in the document.

Actual behavior

 FAIL  functions/src/activities/fieldValue.test.ts
  testFieldValue
    ✕ removes from array (1 ms)

  ● testFieldValue › removes from array

    Value for argument "data" is not a valid Firestore document. Couldn't serialize object of type "ArrayRemoveTransform" (found in field "array"). Firestore doesn't support JavaScript objects with custom prototypes (i.e. objects that were created via the "new" operator).

       8 |
       9 |     if (data) {
    > 10 |       await change.after.ref.set({
         |                              ^
      11 |         array: firestore.FieldValue.arrayRemove("test"),
      12 |       });
      13 |     }

      at validateUserInput (../node_modules/@google-cloud/firestore/build/src/serializer.js:326:15)
      at Object.validateUserInput (../node_modules/@google-cloud/firestore/build/src/serializer.js:263:13)
      at validateDocumentData (../node_modules/@google-cloud/firestore/build/src/write-batch.js:577:18)
      at WriteBatch.set (../node_modules/@google-cloud/firestore/build/src/write-batch.js:243:9)
      at DocumentReference.set (../node_modules/@google-cloud/firestore/build/src/reference.js:349:14)
      at Function.run (src/activities/fieldValue.ts:10:30)
      at wrapped (../node_modules/firebase-functions-test/lib/main.js:72:30)
      at Object.<anonymous> (src/activities/fieldValue.test.ts:44:11)

  ● testFieldValue › removes from array

    expect(received).not.toContain(expected) // indexOf

    Matcher error: received value must not be null nor undefined

    Received has value: undefined

      48 |
      49 |   it("removes from array", () => {
    > 50 |     expect(document?.data()?.array).not.toContain("test");
         |                                         ^
      51 |   });
      52 |
      53 |   afterAll(async () => {

      at Object.<anonymous> (src/activities/fieldValue.test.ts:50:41)

Test Suites: 1 failed, 1 total
Tests:       1 failed, 1 total
Snapshots:   0 total
Time:        1.081 s, estimated 2 s
xanderfehsenfeld commented 3 years ago

When I comment the code like so

import { firestore } from "firebase-admin";
import * as functions from "firebase-functions";

export const testFieldValue = functions.firestore
  .document("activities/{activity}")
  .onWrite(async (change) => {
    const data = change.after.data();

    if (data) {
     // await change.after.ref.set({
     //   array: firestore.FieldValue.arrayRemove("test"),
     // });
    }
  });

or mock firestore.FieldValue.arrayRemove the test doesn't throw an error.

inlined commented 3 years ago

Sorry I'm ramping up on the codebase, but I notice you're calling ref.set with a FieldValue that doesn't make sense in a set command. Does this work if you change set to update?

inlined commented 3 years ago

We will leave this bug open for another week and will close if we do not hear a response.

inlined commented 3 years ago

Closing for now