Giter VIP home page Giter VIP logo

firestore-jest-mock's People

Contributors

a-mroz avatar agustinferrari avatar averagehelper avatar bchiang7 avatar beahuang avatar benedictkhoo avatar catlynbowles avatar corentindoue avatar dependabot[bot] avatar dmiranda2791 avatar frozenkiwi avatar jefshe avatar joshpensky avatar mutagen-d avatar mxp-qk avatar netdown avatar ohneda avatar pusmax avatar sbatson5 avatar shaoshiva avatar tettoffensive avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

firestore-jest-mock's Issues

Internally assert that required parameters are provided in the right order

Summary

While it is useful for users to be able to assert that specific functions are called with the parameters they expect, it may be useful for firestore-jest-mock to make its own assertions that ensure consistency with Firestore's API. For example, the transaction mock doesn't assert that read options, if provided, are the last argument of Transaction's variadic getAll method. It would be easy to accidentally pass that object first, followed by the document refs to get. A nice error message or a warning in the console (somehow) would suit better here than a TypeError when we try to call our mocked DocumentReference methods on the wrong type of object.

Motivation

This sort of check would enforce internal consistency as well as user sanity. We would catch bugs caused by incorrect API calls before the user boots up a Firestore emulator or deploys their product!

Potential Hills

Maintenance would be a problem. If Firestore's API suddenly changes to be more permissive with certain calls (unlikely in the case of variadic methods, but still a possibility) then we would need to update quickly to follow suit. This is why a logged warning may be in order. I don't know how to expose those properly to Jest other than regular console logs (which are invisible in --silent runs), so we may need to think up some solution for that too.

Alternative Directions

Semantic Assertions

We may instead create some way for users to assert the semantics of certain calls. For example, users can already assert that a Transaction object was called upon to update a ref at a specific path since our mock ref gets passed directly to Transaction.update. All one must do is expect that mockTransactionUpdate was called where the first argument includes the path key and a given database path. My company's own projects have already adopted a custom matcher, toHaveFirestorePath, for that very purpose, which may be useful enough to add here in a PR. If we can expand this sort of semantic assertion into more areas, such as direct modifications to Firestore refs (ref.update, for example), which cannot be asserted in this same manner, then users of firestore-jest-mock would be able to replace the current idea of "This is how we interact with Firestore" with "This is how Firestore interprets my instructions." This way, any loss in semantic meaning when test Firestore interactions is caught and tested correctly, even more maintainably.

React Native Support

Summary

Does this library support @react-native-firebase/firestore?

Basic example

I followed the setup instructions in the README for a react native project; however, I get the following error:

Error: You attempted to use a firebase module that's not installed natively on your iOS project by calling firebase.app().

This leads me to believe that the library does not support @react-native-firebase.

Motivation

It would be very helpful to apply this mocking to react native projects!

docChanges() returns an object instead of an array

Description

The method docChanges from buildQuerySnapShot
https://github.com/Upstatement/firestore-jest-mock/blob/cc27ce8aea7549febca5d6ad726aa4403d478c2d/mocks/helpers/buildQuerySnapShot.js#L19

returns an object.

But indeed this methods returns an array
https://github.com/googleapis/nodejs-firestore/blob/c7d67fb749aaf76050c08d29b4c6fca28ec9f5ce/types/firestore.d.ts#L1467
Screenshot from 2021-04-20 16-27-48

Consequently, my tests fail when using this method.

Steps to reproduce

Try to test a code with usage of docChanges in a callback from onSnapshot.
Exemple:

firestore()
    .collection(path)
    .onSnapshot(
      (querySnapshot) => {
        const updates = querySnapshot 
          .docChanges()
          .map((docChange) => ({
            ticket: docChange.doc.data(),
            type: docChange.type,
          }));
        storeApi.dispatch(updateStoredTickets(updates));
      },
    );

Expected result

The test trigger no error

Actual result

Testing the code makes it crash

Environment

  • Ubuntu 20.04
  • Node version: 14.15.5

Proposed fix

return an empty array

Module firebase-admin not found, mocking skipped

Summary

I am trying to set up the mockFirebase in our expo project, however, I get the error:

    console.info
      Module firebase-admin not found, mocking skipped.

      at mockModuleIfFound (node_modules/firestore-jest-mock/mocks/firebase.js:49:13)

Before opening an issue, do I need to install the firebase-admin to be able to use this library in the client?

Installing the firebase admin does make the mocking work, but is it really required? Also, do I need to import firebase twice, on top to init the app, and inside the test block as well, any ideas about what is wrong here?

image

Support @google-cloud/firestore

Summary

@google-cloud/firestore SDK is an alternative to firebase and firebase-admin to interact with Firestore. The exported Firestore class is very similar (probably identical but Im not sure) to firebase.firestore.

Basic example

I use firestore-jest-mock in one of my project with @google-cloud/firestore.
I added this file:

// firestore.mock
import { FakeFirestore } from 'firestore-jest-mock';

type Overrides = {
    database?: Record<string, unknown>;
};

const firestoreMock = (overrides: Overrides) => {
    class Firestore extends FakeFirestore {
        constructor() {
            super(overrides.database);
        }
    }
    return {
        FieldValue: FakeFirestore.FieldValue,
        Firestore,
    };
};

export const mockFirestore = (overrides = {}): void => {
    jest.doMock('@google-cloud/firestore', () => firestoreMock(overrides));
};

And I use mockFirestore exactly as the mockFirebase of firestore-jest-mock:

import { mockFirestore } from './firestore.mock';
import { mockCollection, mockDoc, mockSet } from 'firestore-jest-mock/mocks/firestore';

const getMockedFirestore = async () => {
    mockFirestore();
    const { Firestore } = await import('@google-cloud/firestore');
    return new Firestore({
        projectId: process.env.GCP_PROJECT_NAME,
    });
};

describe('restaurants', () => {
    it('creates the restaurant if it does not exist', async () => {
        const restaurantId = 15;
        const firestore = await getMockedFirestore();

        await firestore.collection('restaurants').doc(restaurantId.toString()).set({ restaurantId });

        expect(mockCollection).toHaveBeenCalledWith('restaurants');
        expect(mockDoc).toHaveBeenCalledWith(restaurantId.toString());
        expect(mockSet).toHaveBeenCalledWith({ restaurantId });
    });
});

Motivation

firestore-jest-mock provides a very useful means to test applications that use Firestore. The Firestore SDK is complicated to mock and the work done in firestore-jest-mock could also help the users of @google-cloud/firestore as well as the ones of firebase.

Implementation

I propose a simple implementation to expose the FakeFirestore according to the signature of @google-cloud/firestore in the linked pull request.

Can I disable the Libraries output?

Summary

I am using this library to unit test my cloud functions, which (evidently ?) do not depend on ('firebase'), however, wherever I am using firestore-jest-mock, I get a console.info
image

Since my project is going to become much bigger, this would mean that I can not use the library as I need to keep the test-restuls as clean as possible! Looking into the code, I couldn't find an "easy toggle" to turn this output off. What can I do?

Bugs in Timestamp

I found some bugs.

  1. https://github.com/Upstatement/firestore-jest-mock/blob/2d98d57050c7b8235b501c5c0effd6fa4111b500/mocks/timestamp.js#L30
    Should be d.setSeconds(this.seconds * 1000);. Also, using setSeconds(), setMilliseconds() etc. comes with some problems - I'll get to this later.
  2. https://github.com/Upstatement/firestore-jest-mock/blob/2d98d57050c7b8235b501c5c0effd6fa4111b500/mocks/timestamp.js#L32
    Should use d.getTime() instead of d.getMilliseconds().
  3. https://github.com/Upstatement/firestore-jest-mock/blob/2d98d57050c7b8235b501c5c0effd6fa4111b500/mocks/timestamp.js#L42
    This will cause the milliseconds to be added twice - once in the nanoseconds part and once as a fraction of the seconds part. It is not explicitly specified in the documentation but I think the intent is that both seconds and nanoseconds would be integers. The implementation from the actual Firebase SDK seems correct to me.
  4. https://github.com/Upstatement/firestore-jest-mock/blob/2d98d57050c7b8235b501c5c0effd6fa4111b500/mocks/timestamp.js#L46-L50
    This problem is a bit difficult to explain. But suppose you are currently at a time zone observing the DST (eg. Finland, May 2) then this will happen:
const now = new Date(); // now = Sun May 02 2021 21:12:49 GMT+0300 (EEST)
const nowFromMilliseconds = new Date(0); // nowFromMilliseconds = Thu Jan 01 1970 02:00:00 GMT+0200 (EET)
nowFromMilliseconds.setMilliseconds(now.getTime()); // nowFromMilliseconds = Sun May 02 2021 20:12:49 GMT+0300 (EEST)

As you can see, nowFromMilliseconds is trailing now by one hour. I've been reading the MDN Web Docs and language specs but I'm still not entirely clear about the mechanics of this. But I think the one hour discrepancy comes from the nowFromMilliseconds starting at a different time zone than where it's landing on when setting the milliseconds. Nevertheless, I think this headache could be prevented by just passing the milliseconds to the Date constructor and not setting milliseconds separately.

Where query on nested fields

Summary

Please correct me if I am wrong, but right now there is no support for querying on nested fields? For example, the code .where("info.startTime", ">=", serverTime) does not work and will not match any documents. Doing the same check on a field that is in the root of the document works fine with simulateQueryFilters set to true.

Is there a way to enable this, or perhaps have this a future feature?

Once again, thank you very much for all the hard work done here.

Requires wrong package for firebase 9

Description

I've tried to use mockFirebase, however it always logs "Module firebase not found, mocking skipped.".

The error comes from here:

https://github.com/Upstatement/firestore-jest-mock/blob/45f435c8b412f7bc9bc8e8d1fac797204e387efc/mocks/firebase.js#L41-L54

It tries to require.resolve('firebase'), however it seems like in Firebase 9 this is not possible anymore (I'm using e.g. require('firebase/firestore') so the mock should likely use similar imports).

Steps to reproduce

Create a project with firebase >= 9. Call mockFirebase().

See this repo for a minimal example: https://github.com/Otto-AA/firestore-mock-bug-demo

Expected result

It should mock firebase.

Actual result

It does not find the firebase module.

Environment

Error `The "path" argument must be of type string.`

Description

I'm getting this error when I try to query a sub collection from within a function:

const path = `mycol/${context.params.id}/mysubcol/${context.params.subcolId}/somethingelse`
const us = await db.collection(path).get()
TypeError [ERR_INVALID_ARG_TYPE]: The "path" argument must be of type string. Received type object

      at GrpcClient.loadProto (node_modules/@google-cloud/firestore/node_modules/google-gax/src/grpc.ts:166:23)
      at new FirestoreClient (node_modules/@google-cloud/firestore/build/src/v1/firestore_client.js:113:32)
      at ClientPool.Firestore._clientPool.pool_1.ClientPool [as clientFactory] (node_modules/@google-cloud/firestore/build/src/index.js:319:26)
      at ClientPool.acquire (node_modules/@google-cloud/firestore/build/src/pool.js:81:35)
      at ClientPool.run (node_modules/@google-cloud/firestore/build/src/pool.js:155:29)
      at _retry (node_modules/@google-cloud/firestore/build/src/index.js:918:30)
      at Firestore._retry (node_modules/@google-cloud/firestore/build/src/index.js:789:38)

Steps to reproduce

NA

Expected result

Query the mocked subcollection.

Actual result

Error

Environment

  • OS: macos
  • Nodejs version: v10.16.3
  • Jest version: 25.1.0
  • Browser and its version: NA
  • Docker version: NA

Firestore mock for DocumentReference.create()

Summary

It seems DocumentReference.create() is not implemented yet.

TypeError: this.db.collection(...).doc(...).create is not a function

Basic example

I would like to test create() function as I actually do with set() function.

Actual

const { mockSet } = require('firestore-jest-mock/mocks/firestore');

test('testing stuff', () => {
  const firebase = require('firebase'); // or import firebase from 'firebase';
  const db = firebase.firestore();

  const data = {property1: 'value1', property2: 'value2'};

  return db
    .collection('users')
    .doc('id')
    .set(data)
    .then(doc => {
      expect(mockSet).toHaveBeenCalledWith(data);
      // Write other assertions here
    });
});

Expected

const { mockCreate } = require('firestore-jest-mock/mocks/firestore');

test('testing stuff', () => {
  const firebase = require('firebase'); // or import firebase from 'firebase';
  const db = firebase.firestore();

  const data = {property1: 'value1', property2: 'value2'};

  return db
    .collection('users')
    .doc('id')
    .create(data)
    .then(doc => {
      expect(mockCreate).toHaveBeenCalledWith(data);
      // Write other assertions here
    });
});

Motivation

DocumentReference.create() allow us to create a new document only if the documentReference does not exist yet. I actually use it in my application, but sadly can't manage to test it.

select is not a function

Summary

I have a query that uses select and is failing with the error above, how do I implement this?. Thanks!

await firestore
      .collection('some')
      .doc(id)
      .where('timestamp', '>=', since)
      .select('field1, 'field2')
      .get()

Error in @google-cloud/firestore compatibility example

Description

In the @google-cloud/firestore compatibility example:

expect(userDocs[0].name).toEqual('Homer Simpson');

fails with:

โ— testing stuff

    expect(received).toEqual(expected) // deep equality

    Expected: "Homer Simpson"
    Received: undefined

      25 |         console.error(userDocs);
      26 |         expect(mockCollection).toHaveBeenCalledWith("users");
    > 27 |         expect(userDocs.docs[0].name).toEqual("Homer Simpson");
         |                                       ^
      28 |       });
      29 | });
      30 |

Solution

Change to:

expect(userDocs.docs[0].data().name).toEqual("Homer Simpson");

or get all docs first, via map():

const result = userDocs.docs.map((doc) => doc.data());

include firebase/admin with 'import' instead of 'require'

Summary

Would prefer import admin from 'firebase-admin' syntax instead of require('firebase-admin')

Relevant information

Including sdk with require('firebase-admin') syntax seems mandatory (regarding docs, sample and my own experience) to make firestore mock work.
Otherwise, the firestore mock is not initialised properly and the tests crash at the first collection call.

As moderns Typescript coders, my team and I would prefer a proper import admin from 'firebase-admin') style of include.
Is there a way to initialize the test, mocks etc properly without that ugly require thing ?
Even adding a jest.mock(admin.firestore...) or something would be more acceptable to us.

You guys did a great job, by the way ;-)

Environment (if relevant)

I can provide test sample if needed, but i gess my question is quite self explanatory.

Isolate Batch logic

Summary

Currently, batch logic is baked into the base FakeFirestore object, which holds the logic for collections and querying. However, WriteBatch is technically it's own object and when you call db.batch() you get an instance of a Batch. We should have a FakeBatch object that better tests batch logic and moves some of the logic out of FakeFirestore

API documentation: https://firebase.google.com/docs/reference/js/firebase.firestore.WriteBatch

The API exposed by this library shouldn't change and everything should be imported through firestore-jest-mock/mocks/firestore -- but a Batch should handle its own logic.

Basic example

const mockCommit = jest.fn();

class FakeWriteBatch {
  commit() {
    mockCommit(...arguments)
  }
  // ...etc
}

Motivation

Easier to maintain code and adds better test coverage (as we don't currently support everything a batch can do).

Is Firestore mock delete() not yet implemented?

Summary

Making calls to delete() a document in a collection does not remove the document.

Relevant information

Looking at /node_modules/firestore-jest-mock/mocks/firestore.js, the implementation for document deletion seems to be missing:

  delete() {
    mockDelete(...arguments);
    return Promise.resolve();
  }

Jest, `mockDoc` validate collection type (v0.18.0)

Summary

Hi!
I have an issue with validating of collection type that doc method was called.
Is any way to get info from mockDoc about collection types that been triggered?

For example:
I get doc from collection named users so then I testing function I should know from mockDoc that it has been called from users collection exactly.
Now I know how many times function has been called or what the arguments have been used.

Relevant information

Maybe I can get a context of called function from mockDoc to someway define the collection type?

Environment (if relevant)

I'm using jest v28.1.3 and firestore-jest-mock 0.18.0

Keep ID in document.data()

Summary

The mocking system strips the id from the mocked doc.data() response. I would like it to keep id in the resulting document.

Basic example

    mockFirebase( {
        database: {
            root: [
                {
                    id: 'root-id',
                    _collections: {
                        subCollection: [
                            {
                                id: 'id-1',
                                test: true
                            },
                        ],
                    },
                },
            ],
        },
    } );
           
   firestore.collectionGroup( 'subCollection' ) .where( 'id', '==', 'id-1' ).get()
           .then((snap) => {
               const resp = snap.docs[0].data();
               // resp is {test: true} needs to be {id: 'id-1', test: true}
            });

Motivation

To allow us to search for a specific document within a collectionGroup we keep the id of our document stored as an id field in the document. So we need to have the mock not strip that out when returning doc.data().
Not sure if it should be an option on mockFirebase, automatic, or another field:
ex. {keepIds: true} or {id: 'id-1', _id: 'id-1'},

Side Question

It seems from my testing that each test file can only truly have 1 mockFirebase as they override each other, is it true that if I want to test say, both getting a Full QuerySnapshot and an Enpty one, that I will need 2 test files? As you cannot set the database field per test.

Checking for mockUpdate within a datasnapshot forEach

Summary

Is it possible to check if mockUpdate was called on a document that is inside of a dataSnapshot.forEach?

// function i am testing: 

...
dataSnapshot.forEach((doc) => {

doc.update({param: 1})
})
...

But when i test using:

expect(mockCollection).toHaveBeenCalledWith('deals');
expect(mockUpdate).toHaveBeenCalled();

mockCollection is passing, but mockUpdate is not

firestore and auth mocks not complete on firebase-admin

Summary

It looks like the mocks for firestore and auth (in tyhe firebase-admin) are outdated

Brief explanation of the feature.
When trying to mock firestore I got errors about recursiveDelete (in firestore) and getUserByEmail (in auth) not being functions. I added recursiveDelete on https://github.com/Upstatement/firestore-jest-mock/blob/master/mocks/firestore.js and getUserByEmail on https://github.com/Upstatement/firestore-jest-mock/blob/master/mocks/auth.js and my tests started passing

Basic example

getUserByEmail() { return Promise.resolve(mockGetUser(...arguments) || {}); }

recursiveDelete() { return Promise.resolve(); }
If the proposal involves a new or changed API, include a basic code example. Omit this section if it's not applicable.

Motivation

Why are we doing this? What use cases does it support? What is the expected outcome?
My workaround so far has being
`
import { FakeFirestore } from 'firestore-jest-mock/mocks/firestore';
Object.setPrototypeOf(FakeFirestore.prototype, {
...Object.getPrototypeOf(FakeFirestore.prototype),
recursiveDelete: jest.fn().mockResolvedValue('ok'),
});

import { FakeAuth } from 'firestore-jest-mock/mocks/auth';
Object.setPrototypeOf(FakeAuth.prototype, {
...Object.getPrototypeOf(FakeAuth.prototype),
getUserByEmail: jest
.fn()
.mockResolvedValue({ id: 'abc123', name: 'Homer Simpson' }),
});
`

Mock Function For collectionGroup Firestore Function

I've been using firestore-jest-mock to mock all of my firestore functions for a project I'm currently working on and it's working great! I've run into one little snag, and that is the collectionGroup Firestore function has not been mocked yet.

What problem does this feature solve?

Include Firestore collectionGroup function as a mocked function.

The collectionGroup function documentation can be found at the following link:

Google API's Firestore

What does the proposed API look like?

There should not be any change to the way mockFirebase is used. If I create a mockFirebase database as seen in the following example:

mockFirebase({
    database: { 
        items: [  
            {id: '1', type: 'item', name: 'This is an item.', owner: 'doge', status: 'much wow'},  
            {id: '2', type: 'item', name: 'This is an item.', owner: 'doge', status: 'so scare'},  
            {id: '3', type: 'item', name: 'This is an item.', owner: 'doge', status: 'many science'},
            {id: '3', type: 'other', name: 'This is not an item.', owner: 'cat', status: 'meow'}   
        ]  
    }  
});

mockFirebase should then mock function calls I am testing. In the following example I would expect the mocked functions to result in the function returning item objects 1, 2, and 3 from the above mockFirebase database example.

const db = firebase.firestore();
const query = db.collectionGroup('items').where('type', '==', 'item);
query.get().then( querySnapshot => {
    return querySnapshot.docs;
}

@google-cloud/firestore Transaction is missing create method.

Description

Transaction in @google-coud/firestore has create method.
It should be implemented.

Steps to reproduce

Try to use transaction.create.

Expected result

Document should be created or fail the transaction if it exists.

Actual result

TypeError: t.create is not a function

Environment

  • Node version: v15.5.0

Types (Typescript)

Summary

Very helpful library!
It would be crazy awesome if it had types or was migrated to typescript.

Almost all the google sdks are typed now, or are migrating to typescript itself.

Add support for onSnapshot

Summary

Firestore has .onSnapshot() which is an alternative to .get() on a collection. It is used to get realtime updates from the DB. This is currently not handled by this mock.

Basic example

        firestore
            .collection( Collections.history )
            .onSnapshot( ( snapshot ) => {
                setHistory(snapshot.docs.map(doc => doc.data() as IHistory));
            } );

Motivation

My code uses quite a few of these to sync my UI with the DB, so to test them I need to be able to mock them and this library is the best option to mock Firebase.
To mock this we should be able to use the same logic as the .get() instead of as a Promise returning the query results as a function callback.

How to test a trigger

Summary

Basically, we have some integration tests that run the triggers against the emulator, but I wanted to have those running against the mocked db instead. Trying to edit a mocked db document doesn't seem to trigger those, the updates are happening correctly but the internal code in the trigger never executes.

Relevant information

I was trying to call the function we have on the trigger, some onUpdate for example, like onUserUpdate is one we want to test, but to directly call those we need to provide the snap and context objects, the snap is defined as a Change which unfortunately I was not able to mock, but the context seems fine as long as you have an object with an EventType it accepts, but complains about trying to parse the Snap as a JSON:|

image

Qs:

  • Is it possible to mock the triggers so we can verify if our trigger helpers are being properly called?
  • Is possible to directly call those triggers somehow?

Once again thank you very much for the hard work done here.

Fetch document with full path doesn't work

Description

When you fetch a specific document with full path, it doesn't find it from the mock database.

Steps to reproduce

mockFirebase({
  database: {
    'mycol/id/mysubcol': [
      { id: 'subId' },
    ],
})

const doc = await lib.db
        .doc(`mycol/id/mysubcol/subId`)
        .get()

But this one does work:

const doc = await lib.db
        .collection(`mycol/id/mysubcol`)
        .doc('subId')
        .get()

Expected result

The document from the database.

Actual result

No result.

Environment

  • OS: macos
  • nodejs

FakeFirestore doesn't declare FieldValue

Description

I'm not quite sure what class is meant to mock over firebase.firestore (is that FakeFirestore?), but whatever it is doesn't seem to declare FieldValue.

Steps to reproduce

  1. Install and configure firestore-jest-mock and Jest.
npm install --save-dev jest firestore-jest-mock@latest
  1. Have source code that uses firebase.firestore.FieldValue. In my case, I have a file that exports an object with a few Firebase helper types:
module.exports = {
  firestore: firebase.firestore(),
  Timestamp: firebase.firestore.Timestamp,
  TIMESTAMP: firebase.firestore.FieldValue.serverTimestamp(),
  increment: firebase.firestore.FieldValue.increment,
  arrayUnion: firebase.firestore.FieldValue.arrayUnion,
  arrayRemove: firebase.firestore.FieldValue.arrayRemove
};
  1. Call mockFirebase in tests, before importing source files under test.
const { mockFirebase } = require("firestore-jest-mock");
mockFirebase({
  database: {
    ...
  }
});
  1. Run jest

Expected result

My tests should run, and fail for different reasons (failed assertions, etc).

Actual result

No tests ran, and this error appeared in the console:

TypeError: Cannot read property 'serverTimestamp' of undefined

   7 |   firestore: firebase.firestore(),
   8 |   Timestamp: firebase.firestore.Timestamp,
>  9 |   TIMESTAMP: firebase.firestore.FieldValue.serverTimestamp(),
     |                                            ^
  10 |   increment: firebase.firestore.FieldValue.increment,
  11 |   arrayUnion: firebase.firestore.FieldValue.arrayUnion,
  12 |   arrayRemove: firebase.firestore.FieldValue.arrayRemove,

Environment

  • Node version: 8.17.0
  • NPM version: 6.14.4
  • Jest version: 25.5.3
  • firebase-admin version: 8.11.0

Cannot fetch values from mocked db

Description

Queries return 0 docs

Steps to reproduce

The following minimal example is built from help docs & tests in this repo:

import { mockFirebase } from "firestore-jest-mock";

mockFirebase({
  database: {
    users: [
      { id: "abc123", name: "Homer Simpson" },
      { id: "abc456", name: "Lisa Simpson" },
    ],
    posts: [{ id: "123abc", title: "Really cool title" }],
  },
});
const firebase = require('firebase');
firebase.initializeApp({
  apiKey: '### FIREBASE API KEY ###',
  authDomain: '### FIREBASE AUTH DOMAIN ###',
  projectId: '### CLOUD FIRESTORE PROJECT ID ###'
});
const db = firebase.firestore();

test('testing stuff', () => {

  db.collection('users')
    .get()
    .then((userDocs: any) => {
      // write assertions here
      expect(userDocs.docs.length).toBe(2);
    });
})

Expected result

test should pass

Actual result

Fails with recieved: 0, expected 2.

Environment

  • Node version:
    Node 12.18.3

Cannot read property 'FirestoreAdminClient' of undefined

I added a function for backing up Firestore database, and now all my tests that use firestore-jest-mock fail due to this error:

    TypeError: Cannot read property 'FirestoreAdminClient' of undefined

      1 | import * as admin from 'firebase-admin'
      2 | import * as functions from 'firebase-functions'
    > 3 | const client = new admin.firestore.v1.FirestoreAdminClient()

The function uses admin.firestore.v1.FirestoreAdminClient type, which doesn't seem to exist in firestore-jest-mock. Any ideas how to fix this?

Separated file usage

Summary

I have my firestore services split into their own files to make mocking the responses the UI needs easier. I have been trying to use this library but can't seem to get these separated files to use the mocked firebase. If I use the db calls directly as shown in the example tests the mocks are called but that doesn't test the files making the calls.

Relevant information

service.js

import firebase from 'firebase';

const firestore = firebase.firestore();

export const getData = () => firestore.collection('data').orderBy('value');

service.test.js

import { getData } from './service';

//works
it('should get ordered data', () => {
    const firebase = require('firebase');
    const db = firebase.firestore();

    db.collection('data').orderBy('value');

    expect(mockCollection).toHaveBeenNthCalledWith(1, 'data');
    expect(mockOrderBy).toHaveBeenNthCalledWith(1, 'value');
  });

//doesn't work
it('should get ordered data', () => {
    getData();

    expect(mockCollection).toHaveBeenNthCalledWith(1, 'data');
    expect(mockOrderBy).toHaveBeenNthCalledWith(1, 'value');
  });

Environment (if relevant)

N/A

Testing with FakeAuth not working: Cannot set property 'sendEmailVerification' of undefined

Description

Trying to test firebase.auth(), but getting an error. I've cloned this repo and the same tests seem to run just fine, but running in my actual code and using the npm package, it fails. Really not sure what it is I'm doing wrong, other than package versions, I'm following the same pattern as in this repo's tests, just inside my own solution. If this is not a defect, and something I'm doing wrong, I apologize for marking it as such.

Steps to reproduce

test('signInUser', async () => {
    expect.assertions(1)
    mockFirebase({
      database: {
        users: [
          { id: 'abc123', name: 'Homer Simpson' },
          { id: 'abc456', name: 'Lisa Simpson' },
        ],
      },
    })
    const firebase = require('firebase')
    firebase.initializeApp(firebaseConfig)

    await firebase.auth().signInWithEmailAndPassword('sam', 'hill')
    expect(mockSignInWithEmailAndPassword).toHaveBeenCalledWith('sam', 'hill')
  })

Expected result

The mock for signInWithEmailAndPassword should be called

What happened.

TypeError: Cannot set property 'sendEmailVerification' of undefined

      35 |     firebase.initializeApp(firebaseConfig)
      36 | 
    > 37 |     await firebase.auth().signInWithEmailAndPassword('sam', 'hill')
         |                    ^
      38 |     expect(mockSignInWithEmailAndPassword).toHaveBeenCalledWith('sam', 'hill')
      39 |   })
      40 | })

      at new FakeAuth (node_modules/firestore-jest-mock/mocks/auth.js:10:39)
      at Object.auth (node_modules/firestore-jest-mock/mocks/firebase.js:14:14)
      at Object.<anonymous> (tests/unit/services/authorization/index.spec.js:37:20)

Environment

  • Node version: 12.14.1
    "firebase": "^7.19.0",
    "firebase-admin": "^9.1.1",
    "firestore-jest-mock": "^0.5.0",
    "jest": "^26.4.2",

Getting "FirebaseError: Firebase: No Firebase App '[DEFAULT]' has been created"

Description

From the readme it sounds like I should be able to do this:

import firebase from "firebase";
const { mockFirebase } = require("firestore-jest-mock");

describe("Database", () => {

  mockFirebase({
    database: {
      users: [
        { id: "abc123", name: "Homer Simpson" },
        { id: "abc456", name: "Lisa Simpson" },
      ],
      posts: [{ id: "123abc", title: "Really cool title" }],
    },
  });
  const db = firebase.firestore();

  it("can do something", () => {...});
});

However, the line const db = firebase.firestore(); fails with FirebaseError: Firebase: No Firebase App '[DEFAULT]' has been created - call Firebase App.initializeApp() (app/no-app).

Expected result

I should be able to initialize the DB.

Environment

  • Node version: 12.13.1
  • Firebase version: 7.17.1
  • firestore-jest-mock version: 0.5.0
  • Running on macOS 10.15.2

beforeEach clearAllMocks not working

Description

Basically what the title says. I have two tests and when i test them separately they have correct values, but when i run therm in bulk mockFirebase() with the values from the test above go to the one below despite the clearAllMocks method.
Describe the issue that you're seeing.

describe('getDetails', () => {
  let spyCon = spyConsole();

  beforeEach(() => {
    jest.clearAllMocks();
  });

  it('returns formatted details', async () => {

    mockFirebase({
      database: {
        collection: [
          {
            testingValOne: 'hello',
            testingValTwo: 'world',
     
          },
        ],
      },
    });
    const firebase = require('firebase'); 
    const db = firebase.firestore();

    const result = await  handleInfo.getDetails(db, mockAuth);

    expect(mockWhere).toHaveBeenCalledWith('payerID', '==', '123');
    expect(result).toEqual({
      testingValOne: 'hello',
      testingValTwo: 'world',
    });
  });

  it('logs out a 404 error', async () => {
    mockFirebase({
      database: {
        collection: [],
      },
    });

    const firebase = require('firebase'); 
    const db = firebase.firestore();

    await handleInfo.getDetails(db, mockAuth);

    expect(console.error).toHaveBeenCalledWith('404 resource not found');
  });


});

The mock data from 'test returns true' gets passed to 'logs out a 404 error' where im trying to mock an empty collection.

Not sure if its the issue with jest, or this module to be honest, but this module being out of the norm i though ill ask here.

EDIT: managed to get it to get it to work with afterEach(()=> {jest.resetAllModules()}), though im not so sure about this solution if the tests potentially will have to share them.

EDIT2: Yeah, that was very shortlived. As soon as i started adding different tests and mocks that broke. The particularly temperamental ones are mockUpdate, mockDoc, mockCollection etc. Even clearing them directly with mockClear() doesnt work

EDIT3: ok, managed to get it to work after the last attempt and moving out the mockFirebase and db beyond the scope of describe fixed it with beforeEaach(()=>jest.clearAllMocks()) working.
Even though I did it differently than in the example provided (mocking db in each test),which should be an obvious clue. there was a good reason for it. The reason being that mocked where doesnt filter the response from the mocked db and some of the logic depended on the response from the db which naturally made me think that mocking db in each test with different data would solve the problem. Also, each test, when run separately, worked. Just when running them all together the mocked values werent getting reset.
Which leads me to another question. How to handle a situation where the logic is dependent on the response of the "where" query?
Clearly mocking firestore in each test doesnt work.

Example of multiple tests with different database data?

Hello, firstly thank you for creating this wonderful library! I've been using it over the last week to write some very useful tests for my project.

I am not sure if this is a feature request, support question or bug!

I am wondering if there are any good examples of how to setup different database data for tests within one code file? I am finding that I have to use requires to import everything that uses firebase or else they get imported before the mocking happens and it doesn't work.

This seems to also mean that only the first mockGoogleCloudFirestore() is used within my tests.

Is there a way of storing an instance of mockGoogleCloudFirestore and adjusting the database? For now I am using mutable: true and managing the data between tests, but this is a lot of what feels unnecessary work.

Ideally I would also like to remove the need for using requires, as this is breaking my typescript and makes the tests harder to manage (if an imported file in tests starts using firebase then it has to be moved into a require or seemingly unrelated tests will start failing).

I am hoping that I am just missing something obvious here, but if not I would love a way of adjusting the data. I have provided a failing example test below:

import { mockGoogleCloudFirestore } from 'firestore-jest-mock';

describe('some tests', () => {
  it('Should do the first thing', async () => {
    mockGoogleCloudFirestore({
      database: {
        test: [{ id: '1', someField: true }],
      },
    });
    const { Firestore } = require('@google-cloud/firestore');
    const firestore = new Firestore();
    const results = await firestore.collection('test').get();
    expect(results.docs[0].data().someField).toEqual(true); // WORKS
  });

  it('Should do the second thing', async () => {
    mockGoogleCloudFirestore({
      database: {
        test: [{ id: '1', someField: false }],
      },
    });
    const { Firestore } = require('@google-cloud/firestore');
    const firestore = new Firestore();
    const results = await firestore.collection('test').get();
    expect(results.docs[0].data().someField).toEqual(false); // FAILS
  });
});
npm version: v8.12.1
Node version: v18.0.4
"firebase-functions-test": "2.4.0",
"@google-cloud/firestore": "6.0.0",

Firebase Auth module has a different structure with Firebase-Admin Auth module

Description

You all did a great job with mocking two modules which are

  • firebase &
  • firebase-admin

KUDOS ๐Ÿ‘ ๐Ÿ†

However, there is an assumption that the Auth module in the firebase module would be the same as the Auth module in firebase-admin but they are different i.e with different methods and properties

For more clarity:
This is the Auth module for Firebase Admin
This is the Auth module for Firebase

if you scroll to methods, you will see that the method list is different.

Steps to reproduce

Try to use the auth module from firebase-admin, e.g

import * as admin from 'firebase-admin';
admin.initializeApp();

const auth = admin.auth();

auth.createUser({ email: '[email protected]', password: 'password', displayName: 'john doe' })

Expected result

  • Have createUser built-in as part of the FakeAuth class
    OR
  • Create the flexibility to pass it in as a property in the overrides object

Actual result

TypeError: firebase_1.auth.createUser is not a function

Here is a link to a possible workaround solution
https://gist.github.com/johnkingzy/9097b5b65fc8a472ed51a35d7a0aba45
TLDR: I redefined the FakeAuth class to match the module structure for firebase-admin

Environment

  • Local
  • Node version: v15.4.0

Support auth signOut

Summary

Firebase Auth sign out is not currently part of the available mocks to assert on

Basic example

โ€‹importโ€‹ย โ€‹{โ€‹ย โ€‹mockSignOutโ€‹ย โ€‹}โ€‹ย โ€‹fromโ€‹ย โ€‹'firestore-jest-mock/mocks/auth'โ€‹;

it('signs out',  async () => {
    const firebase = require('firebase');
    const auth = firebase.auth();

    await auth.signOut();

    expect(mockSignOut).toHaveBeenCalled():
});

Motivation

This is not currently part of the available mocks and would be helpful just like the sign in

Support withConverter

Summary

In firebase a Query, CollectionReference and DocumentReference can use the withConverter method, this is not currently part of the mocks or faked query, document or collection

Basic example

const userConverter = {
  toFirestore({ permission }) {
    return { permission };
  },
  fromFirestore(snapshot, options) {
    const { permission } = snapshot.data(options);
    return new User(permission);
  }
};

firestore.collection('users').withConverter(userConverter).get();

Motivation

The above example throws an error firestore.collection(...).withConverter is not a function as there is no withConverter implemented into the faked query, document or collection. I use these converters to avoid duplication of conversions but cannot use if testing with this library.

Batch Commit should return an array

Summary

Currently the Batch Commit returns undefined, to not break systems that use the WriteResult[] response an array should be returned instead

Basic example

        const result = await batch.commit();
        logger.info( 'Records Saved:', result.length );

Motivation

Without needing to mock the full WriteResult object an empty array would make sure things like length, forEach and map won't break.

Proposed Change

      commit() {
        mockBatchCommit(...arguments);
        return Promise.resolve([]);
      },

How does subcollection mocking work? Does it?

Summary

Now that FakeFirestore.FieldValue works, my unit tests can progress a little bit more. I'm now running into trouble where subcollection access matters.

I have code that access the database like so:

const snap = await firestore
  .collection("accounts")
  .doc(accountId)
  .collection("userPermissions")
  .doc(userId)
  .get();

This returns a snapshot whose document doesn't exist. (That is, the object returned by the mock is simply { exists: false }) This needs to not be the case.

I have my firestore mock declared like so:

mockFirebase({
  database: {
    accounts: [ // Root-level collection
      { // A document
        id: "testAcct",
        conversations: [ // Subcollection, right?
          { id: "testConvo", participants: ["right-user", "admin-user"] },
        ],
        userPermissions: [ // Subcollection, right??
          { id: "right-user", level: "normal" },
          { id: "admin-user", level: "admin" },
          { id: "owner", level: "super" },
        ]
      },
    ]
  }
});

Am I missing something here? Is this my bad, or does the package not yet support mocking subcollections?

Relevant information

I should note that I see no mention in the package about subcollections, so I can only assume based on the root case that this is how subcollections are meant to be represented.

To further edge the point, I threw a test case on the package, which sadly fails:

const db = new FakeFirestore({
  characters: [
    { id: 'homer', name: 'Homer', occupation: 'technician' },
    { id: 'krusty', name: 'Krusty', occupation: 'clown' },
    { // I added this document only
      id: 'bob',
      name: 'Bob',
      occupation: 'repairman',
      family: [ // I see this as a subcollection.  Am I wrong, or is the mock?
        { id: 'thing1', name: 'Thing 1', relation: 'pet' },
        { id: 'thing2', name: 'Thing 2', relation: 'pet' },
        { id: 'deborah', name: 'Deborah', relation: 'wife' },
      ],
    },
  ],
});

test('it can fetch records from subcollections', async () => {
  expect.assertions(2);
  const record = await db
    .collection('characters')
    .doc('bob')
    .collection('family')
    .doc('thing1')
    .get();
  expect(record.exists).toBe(true);  // Expected: true, Received: false
  expect(record.data()).toHaveProperty('name', 'Thing 1');
});

Environment (if relevant)

firestore-jest-mock 0.3.0

Firestore instance in snapshot.ref go undefined after make a document query request to the database

Description

The QueryDocumentSnapshot passed to onCreate() trigger failed to retrieve FakeFirestore instance within its ref value after query a document from the database.

Steps to reproduce

  1. Write a onCreate() functions trigger
    export const setupActionTrigger = () => {
      return functions.firestore.document('note/{id}').onCreate(async(snapshot) => {
        if(snapshot.exists){
          console.log(snapshot.ref);
          const userRef = await admin.firestore().doc(`users/123`}).get();
          console.log(snapshot.ref);
        }
      });
    }
    
  2. Make a test class
 describe('actionTrigger', () => {
   const test = firebaseFunctionsTest(); 
   let actionTriggerFunction: WrappedFunction;
   
   beforeAll(() => {
     fakeFirestore = new FakeFirestore({
       note: [{id: '123', note: 'test note', uid: 'xyz'}],
       users: [{id:'xyz', username: 'xyzlmn'}]
     });
     mockFirebase({database: fakeFirestore.database});
     admin.initializeApp();
     
     const {setupActionTrigger} = require('../src/test-trigger');
     actionTriggerFn = test.wrap(setupActionTrigger());
   }

   asterAll(() => {
     jest.clearAllMocks();
     jest.resetAllMocks();
     test.cleanup();
   });

  it('should pass', async () => {
    const doc = await fakeFirestore.doc('note/123').get();
    await actionTrigger(doc);
  })
 }
  1. Note that the first console.log in the trigger function for snapshot.ref print with FakeFirestore instance that provided by test suit.
  2. Note that the second console.log in the trigger function print without FakeFirestore instance, instead it says firestore:undefined

Expected result

Firesore instance with provided FakeFirestore instance would remain untouched with snapshot object after retrieving a document from the database query as shown in the trigger function

Actual result

The snapshot object reference FakeFirestore object went undefined after making the database query

Environment

  • Node version: 12.20.1

firebase-admin: Unable to detect a Project Id in the current environment.

Summary

I'm trying to use this with firebase-admin, I have a mockFirebase with the database on it on the first describe of my tests, then with a beforeEach I call

beforeEach(() => {
    admin.initializeApp({
      credential: admin.credential.cert({
        projectId: process.env.FIREBASE_PROJECT_ID,
        privateKey: process.env.FIREBASE_PRIVATE_KEY.replace(/\\n/g, '\n'),
        clientEmail: process.env.FIREBASE_CLIENT_EMAIL,
      }),
      databaseURL: process.env.FIREBASE_DATABASE_URL,
    })
  })

For some reason, when my function tries to call firebase, I get the following error:

Unable to detect a Project Id in the current environment. 
   To learn more about authentication and Google APIs, visit: 
   https://cloud.google.com/docs/authentication/getting-started
at node_modules/google-gax/node_modules/google-auth-library/build/src/auth/googleauth.js:87:31
 Caused by: Error: 
 at CollectionReference._get (node_modules/@google-cloud/firestore/build/src/reference.js:1449:23)
 at CollectionReference.get (node_modules/@google-cloud/firestore/build/src/reference.js:1438:21)

Any ideas of what I'm missing for my firebase to mock properly?

Error in where query

First of all, I want to appreciate the developers for such a great project.

Description

Where query seems to return all documents, I think it should actually filter out the actual result instead of returning all result.

Steps to reproduce

Create a sample test and query with the where query
It returns all results instead of the exact document that matches the where conditions.

For example, I have validation in my code to check if a user exists then I throw some error.

    const existingUser = await db
      .collection('users')
      .where('email', '==', email.toLowerCase())
      .get();
    
    if (!existingUser.empty) {
      throw new Error('User already exist');
    }

I am unable to test this condition using this package since it returns all documents in the collection, irrespective of the query condition.

Expected result

It should return only a document that matches the where query.

Actual result

It returns all documents in the collections.

What happened.

Environment

  • Node version:

How to add current Logged in User

I want to know how to add a current logged in user for the mock firebase. I have a function in a different file from my test file with the function:

export const getUserDocument = async (uid) => {
    if (!uid) return null;
    try {
        const userDocument = await firestore.doc(`users/${uid}`).get();
        return {
            uid,
            ...userDocument.data(),
        };
    } catch (error) {
        console.error("Error fetching user", error);
    }
};

Then in a separate test file I put:

it("should return a user if the function is called with a uid provided", async () => {
        //Promises need to be returned to properly throw errors
        return getUserDocument("a1").then((uid, data) => {
            //expect(mockCollection).toHaveBeenCalledWith('users');
            expect(mockDoc).toHaveBeenCalledWith("users/a1");
            expect(uid).toEqual("a1");
            expect(data).not.toBeNull();
        });
    });

Expected behavior: the function returns the uid of "a1" and some data
Actual Behavior:

console.error
    Error fetching user Error [FirebaseError]: Missing or insufficient permissions.
        at new FirestoreError (/Users/anon/Documents/GitHub/PathwayWeb/node_modules/@firebase/firestore/src/util/error.ts:217:5)
        at fromRpcStatus (/Users/anon/Documents/GitHub/PathwayWeb/node_modules/@firebase/firestore/src/remote/serializer.ts:154:10)
        at fromWatchChange (/Users/anon/Documents/GitHub/PathwayWeb/node_modules/@firebase/firestore/src/remote/serializer.ts:476:33)
        at PersistentListenStream.Object.<anonymous>.PersistentListenStream.onMessage (/Users/anon/Documents/GitHub/PathwayWeb/node_modules/@firebase/firestore/src/remote/persistent_stream.ts:581:25)
        at /Users/anon/Documents/GitHub/PathwayWeb/node_modules/@firebase/firestore/src/remote/persistent_stream.ts:461:21
        at /Users/anon/Documents/GitHub/PathwayWeb/node_modules/@firebase/firestore/src/remote/persistent_stream.ts:514:18
        at /Users/anon/Documents/GitHub/PathwayWeb/node_modules/@firebase/firestore/src/util/async_queue_impl.ts:168:14
        at processTicksAndRejections (internal/process/task_queues.js:93:5) {
      code: 'permission-denied',
      name: 'FirebaseError',
      toString: [Function]
    }

      86 |         };
      87 |     } catch (error) {
    > 88 |         console.error("Error fetching user", error);
         |                 ^
      89 |     }
      90 | };

It would seem I need to authenticate first which doesn't make sense because the mock library isn't suppose to have any installed rules, unless it's not using the mock library

Assert access on a Firestore document path

Summary

I propose we add some way for users to assert access to specific document or collection paths.

Basic example

We should be able to assert transaction or batched writes like this:

// The update call:
expect(mockUpdateTransaction).toHaveBeenCalledWith(
  // The document ref:
  expect.toHaveFirestorePath(`accounts/${accountId}/users/${userId}`),
  // The data:
  { ... }
);

Here we assert that the Firestore API was called with the expected parameters while asserting that one of those parameters defined a specific Firestore document path.

Standalone reads and writes are a bit trickier, since calling ref.update({ ... }) doesn't presently inform mockUpdate about the path on which the access was made. I don't have any good ideas for that yet, but at the very least we should have some matchers for our mock DocumentReference or CollectionReference types.

Motivation

Since we now have subcollection support as of #35, users now have a problem when trying to assert access to a specific document path. I do not mean access permissions, those are handled by Firestore rules and beyond the scope of firestore-jest-mock. Presently, the normal way to assert correct document access is by using the variations of the following logical argument:

  1. mockDoc was called after mockCollection, using either expect(...).toHaveBeenCalledAfter or expect(...).not.toHaveBeenCalledBefore, preferably both.
  2. mockCollection was called with the correct collection ID c, using expect(...).toHaveBeenCalledWith, or more preferably expect(...).toHaveBeenNthCalledWith.
  3. mockDoc was called with the correct document ID d, using expect(...).toHaveBeenCalledWith, or more preferably expect(...).toHaveBeenNthCalledWith.
  4. Therefore, we can be certain that the referenced Firestore document path was c/d.

This works well enough when the unit under test only deals with one Firestore document, and calls these methods exactly once each. But that is often not the case. Nested subcollections break the assumption that mockCollection was called only before mockDoc, and therefore our conclusion no longer holds true in every case. We now need to factor in which call to mockDoc or mockCollection we're asserting against, and then any reordering of accesses breaks unit tests, even when such access has only to do with the construction of DocumentReferences.

Consider the following operation on two account-scoped documents that each represent an account member:

const accountA = ...;
const accountB = ...;
const userId = ...;

const userRef = db
  .collection("accounts")
  .doc(accountA)
  .collection("users")
  .doc(userId);
const otherUserRef = db
  .collection("accounts")
  .doc(accountB)
  .collection("users")
  .doc(userId);

A common reason for preparing DocumentReferences like these is to use them in a transaction or a batch write operation, sometimes both:

await db.runTransaction(async transaction => {
  const user = await transaction.get(userRef);
  ...
  transaction.update(otherUserRef, { ... });
});

As of today, we have no way to assert directly that "the document at path `accounts/${accountId}/users/${userId}` was updated with { this data }". Today's methods, which can easily confuse assertions on userRef with assertions on otherUserRef, rely on the following assertions in Jest:

  • mockCollection was called four times, twice with the argument "accounts" and twice with the argument "users". We assert this using four toHaveBeenNthCalledWith assertions, and one toHaveBeenCalledTimes assertion for good measure.
  • mockDoc was called four times, once with the argument accountA, once with the argument accountB, and twice with the argument userId. We assert this in the same verbose manner as we did mockCollection.
  • mockUpdateTransaction was called once with the first argument being an instance of FakeFirestore.DocumentReference and the second being { the data }. To assert more than this requires reconstructing the same document reference using the mocked Firestore database (which may involve an import or a require). This is a nontrivial operation.

We cannot clearly assert the order of calls to mockCollection and mockDoc without doing a fancy dance about which call with which arguments came before which other call with which arguments, which will always be complicated by the fact that we call collection("accounts") twice. We may simplify this call, but the issue remains about the ordering of the other calls. Test cases quickly become very bulky and difficult to maintain.

Proposed Solution

Asserting specific document paths can be done with a simple Jest matcher!

FakeFirestore.DocumentReference has a path property which contains a string similar to the one canonical to Firestore.DocumentReference. In theory, we all we need to do to assert that a transaction.update call occurred on the correct path is to assert that the call's first argument has a path property and that its value is the expected document path.

We may define the matcher with something like the following TypeScript code:

function toHaveFirestorePath(
  this: jest.MatcherUtils,
  ref: { path: string },
  path: string
): jest.CustomMatcherResult {
  if (typeof path !== "string") {
    throw new Error(`Expected 'path' to be a string. Got ${typeof path}: ${path}`);
  }
  const pass =
    ref &&                           // truthy
    typeof ref === "object" &&       // is an object
    "path" in ref &&                 // has a "path" property
    typeof ref.path === "string" &&  // ref.path is a string
    ref.path.startsWith(path);       // ref.path is at least a child of the expected path

  // The message only appears when the test fails (whether by passing when it was expected not to
  // or by not passing when it was expected to)
  return {
    message: () => `Expected '${path}' ${pass ? "NOT" : ""}to be '${ref.path}'`,
    pass
  };
}

This code uses the path property of FakeFirestore.DocumentReference or FakeFirestore.CollectionReference to check that the value under test is a child of the provided path.

We use the matcher in test cases like so:

// the unit under test
async function doTheThing() {
  const ref = db.collection("accounts").doc("accountA").collection("users").doc("userA");
  await db.runTransaction(async transaction => {
    ...
    await transaction.get(ref);
    ...
  });
}

// the test
beforeEach(async () => {
  await doTheThing();
});

test("reads from account A", () => {
  expect(mockGetTransaction).toHaveBeenCalledWith(
    expect.toHaveFirestorePath(`accounts/accountA`)
  );
});

With one simple assertion, we can prove that the unit accessed some document under accounts/accountA. It is now trivial to assert other important parts of the Firestore call, such as:

  • ensuring that the document was only read once.
  • the read occurred before any write call to the same path.
  • all calls were made against a document at a specific path.

Other matchers may be written to handle more granular or more specific cases as needed.

Further Work

FakeFirestore.DocumentReference#path

The canonical Firestore.DocumentReference object has a path property, but in designing FakeFirestore.DocumentReference I did not consider the structure of the canon version. This may break some code that relies on the value of that path property. We should update our implementation to match Firestore's.

#102 makes document paths more closely match Firestore's implementation to the best of my knowledge.

What about single I/O calls?

The matcher described in this proposal only extend Jest's present ability to assert properties of arguments to mock function calls. As far as I am aware, Jest does not have an easy way to assert properties of objects on which mock methods were called. Some restructuring may be necessary to permit that capability in a similar fashion. Suggestions would be appreciated.

EDIT: IDEA!! IIRC, Firestore canonically returns a document ref or document snapshot as the result of a write operation. We might be able to assert the path of that return value in a Jest matcher.

Can't mock firestore via firebase-admin

Description

Struggling to get a test passing

Steps to reproduce

const { mockFirebase } = require("firestore-jest-mock");

mockFirebase({
  database: {
    users: [
      { id: "abc123", name: "Homer Simpson" },
      { id: "abc456", name: "Lisa Simpson" },
    ],
    posts: [{ id: "123abc", title: "Really cool title" }],
  },
});

const { mockCollection } = require("firestore-jest-mock/mocks/firestore");

test("testing stuff", () => {
  const admin = require("firebase-admin"); // or import firebase from 'firebase';
  admin.initializeApp();
  const db = admin.firestore();

  return db
    .collection("users")
    .get()
    .then((userDocs) => {
      console.log(userDocs);
      expect(mockCollection).toHaveBeenCalledWith("users");
      expect(userDocs.docs[0].data().name).toEqual("Homer Simpson");
    });
});

Expected result

Test pass

Actual result

Fails, firestore not mocked

Screenshot 2021-05-19 at 15 34 45

Environment

  • Node version:

How would I layout subcollections in the mock database?

Summary

I'm trying to mock Firestore data that's a few layers deep in the collection/document tree. In my testing so far, the mocked get() function doesn't resolve for more than 5 seconds, and then Jest times out.

Relevant information

My mock tree is laid out like so:

mockFirebase({
  database: {
    accounts: [  // collection, right?
      {
        id: testAcctId,
        conversations: [  // subcollection, right?
            { id: testConversationId, participants: ["someone", "someone-else"] }
        ],
        userPermissions: [  // another subcollection, right?
          { id: "someone", level: "normal" },
          { id: "someone-else", level: "admin" }
        ]
      }
    ]
  }
});

My coverage report says the test function stops at this call:

const { firestore } = require("../my/firestoreSetupFile"); // We reexport require("firebase-admin") from here
const userPermissionsSnapshot = await firestore
      .collection("accounts")
      .doc(accountId)
      .collection("userPermissions")
      .doc(userId)
      .get();  // We hang here ~5 seconds before timeout. What's up with that?

The way I understand it, and based on the README, it seems that firestore-jest-mock interprets arrays of objects with the id attribute to denote subcollections of documents. Am I incorrect in that assumption? And could it be that it only assumes that collections are at the top level? If not, do I have the syntax right to mock a database with 2+ layers of subcollections? I have more, but it is at this point that my tests fail to run properly, and I'm not a little confused.

Environment

I'm using Jest 25.5.3

QueryDocumentSnapshot that passed to trigger functions doesn't contain createTime element

Description

QueryDocumentSnapshot that passed to trigger functions doesn't contain createTime element

Steps to reproduce

Call createTime in snapshot passed into a trigger function.

Expected result

The createTime should contain a value of the document created in the original scenario.
https://googleapis.dev/nodejs/firestore/latest/QueryDocumentSnapshot.html

Actual result

createTime element is not available within the queryDocumentSnapshot that passed to trigger functions

Fix suggesstion

For mocking purposes, we can provide the current time of the test suit or createdAt time if available on the FakeFirestore database.
In order to correct the issue, buildDocFromHash.js file should return an object containing populated createTime.
For example:

module.exports = function buildDocFromHash(hash = {}, id = 'abc123') {
  const exists = !!hash || false;
  return {
    exists,
    id: (hash && hash.id) || id,
    ref: hash && hash._ref,
    createTime: hash.createdAt,
    metadata: {
      hasPendingWrites: 'Server',
    },
  ...

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.