firebase / firebase-functions-test

MIT License
232 stars 48 forks source link

"Error: Cannot encode [object Object]to a Firestore Value. Local testing does not yet support Firestore geo points" when using DocReference/Timestamp #101

Closed ChromeQ closed 3 years ago

ChromeQ commented 3 years ago

This is a very similar problem raised #17 (Error: Cannot encode [object Object]to a Firestore Value. Local testing does not yet support Firestore geo points.) here but I am not using GeoPoints, I am adding a document reference to another document.

Also happens to document containing a firestore Timestamp

Version info

firebase-functions-test: 0.3.0 firebase-functions: 3.14.1 firebase-admin: NOT REQUIRED @firebase/testing: 0.20.11

Test case

import * as firebase from '@firebase/testing';
import * as functions from 'firebase-functions';
import functionsTest from 'firebase-functions-test';

const app = firebase.initializeAdminApp({ projectId: 'my-project' });
const firestore = app.firestore();
const testEnv = functionsTest();

// example function trigger
const userCreated = functions.firestore.document('/users/{userID}').onCreate((snap) => {
    console.log(`user created: ${snap.id}!`);
});

// wrap the function
const wrapped = testEnv.wrap(userCreated);

it('test fails', async () => {
    // create a user and store the reference
    await firestore.collection('users').doc('user1').set({ name: 'Mrs. Jones' });
    const ref = firestore.collection('users').doc('user1');

    // create new user linked to the reference above
    const newUser = {
        name: 'Tom Jones',
        spouse: ref,
    };

    // try to create a snapshot of new user to pass to userCreated function
    const snap = testEnv.firestore.makeDocumentSnapshot(newUser, '/users/user2');
    const prom = wrapped(snap);

    prom.then(() => {
        return console.log('complete');
    }).catch((error: unknown) => {
        return console.error(error);
    });
});

Steps to reproduce

Execute test case provided, error is thrown during the call to test.firestore.makeDocumentSnapshot.

Expected behavior

Snapshot created successfully, function is execution is emulated as is trigger has occurred.

Actual behavior

Call to test.firestore.makeDocumentSnapshot fails due to GeoPoint in the data model.

Produces error:

Cannot encode [object Object]to a Firestore Value. Local testing does not yet support Firestore geo points.

ChromeQ commented 3 years ago

I've stepped into the code and found the problem seems to arise here: https://github.com/firebase/firebase-functions-test/blob/master/src/providers/firestore.ts#L185 For some reason the document I pass in is not an instanceof firestore.DocumentReference. Looking at that file firestore is imported from 'firebase-admin'.

So, I changed my test to import firebase and initialize firestore using firestore-admin:

import * as firebase from 'firebase-admin'; // v9.9.0
import * as functions from 'firebase-functions';
import functionsTest from 'firebase-functions-test';

const app = firebase.initializeApp({ projectId: 'my-project' });
const firestore = app.firestore();
const testEnv = functionsTest();

But the same result: Cannot encode [object Object]to a Firestore Value. Local testing does not yet support Firestore geo points.

ChromeQ commented 3 years ago

Finally, I found a stack overflow with a similar issue: https://stackoverflow.com/questions/58867153/timestamp-not-working-with-firebase-functions-test

The answer suggests to create a new Timestamp but in my case a new Documentreference which makes sense to pass the if (val instanceof firestore.DocumentReference) { but I cannot seem to do that (typescript complaints as it is a private class) and seems like not the most common way to create a document reference anyway.

Constructor of class 'DocumentReference<T>' is private and only accessible within the class declaration.ts(2673) image

i14h commented 3 years ago

thanks @ChromeQ for looking into this. @joehan can you take a look at the issue?

inlined commented 3 years ago

You don't create a DocumentReference with the new operator. You create one with firebase.firestore.document('foo/bar')

ChromeQ commented 3 years ago

Yes that is the point I was making, to be able to pass the instanceof firestore.DocumentReference check you'd need to call new Documentreference which is not the correct way.

But looks as though this is fixed in v0.3.1 and #47 is closed by #105