0

I'm using firebase-tools emulator to locally test saving a record to Cloud Firestore.

$ firebase serve --only functions,firestore
i  firestore: Serving ALL traffic (including WebChannel) on http://localhost:8080
⚠  firestore: Support for WebChannel on a separate port (8081) is DEPRECATED and will go away soon. Please use port above instead.
i  firestore: Emulator logging to firestore-debug.log
⚠  Your requested "node" version "8" doesn't match your global version "12"
✔  functions: Emulator started at http://localhost:5000
✔  firestore: Emulator started at http://localhost:8080

My code:

// FirestoreConnection.ts
import {firestore} from "firebase-admin";

export default class FirestoreConnection {
  protected shopDomain: string;
  protected database: firestore.Firestore;

  constructor(shopDomain: string, database: firestore.Firestore) {
    this.shopDomain = shopDomain;
    this.database = database;
  }


   // --------------- Public Methods

  public async createNew(type: RecordTypes, documentData: object): Promise<firestore.DocumentSnapshot|null> {

    try {
      return await this.addDocument(collectionName, documentData);
    }
    catch (e) {
      console.log(e, `=====error createNew()=====`);
      return null;
    }
  }

   // --------------- Protected/Private Methods

  protected async addDocument(collectionName: string, documentData: object): Promise<firestore.DocumentSnapshot|null> {
    try {
      const newlyAddedDocument = await this.database
        .collection(collectionName)
        .add(documentData);

      return await newlyAddedDocument.get();
    }
    catch (e) {
      console.log(e, `=====error addDocument()=====`);
      return null;
    }
  }


// FirestoreConnection.test.ts
import * as firebaseTesting from "@firebase/testing";
import {Logger} from "@firebase/logger";
import {RecordTypes} from "../../../../shared";

import FirestoreConnection from "../FirestoreConnection";


/* * * * * * * * * * * * * * * * * * * * *
                  Setup
* * * * * * * * * * * * * * * * * * * * */

// setup firebase logging
const logClient = new Logger("@firebase/testing");
logClient.log("FirestoreConnection.test.ts");

// --------------- Helpers

const createTestDatabase = (credentials): any => {
  return firebaseTesting
    .initializeTestApp({
      projectId: 'testproject',
      auth: credentials
    })
    .firestore();
};

const nullAllApps = firebaseTesting
  .apps().map(app => app.delete());

const db = new FirestoreConnection('testshopdomain', createTestDatabase(null));


// --------------- Before / After

beforeEach(() => {
});


afterEach(async () => {
  try {
    await Promise.all(nullAllApps);
    await firebaseTesting.clearFirestoreData({
      projectId : "testproject"
    })
  }
  catch (e) {
    console.log(e, `=====error=====`);
  }
});


/* * * * * * * * * * * * * * * * * * * * *
                  Tests
* * * * * * * * * * * * * * * * * * * * */
test("creates new record", async () => {
  try {
    const addedDocument = await db
      .createNew(RecordTypes.globalRule, {
        storeId : "dummystoreid"
        , globalPercent : 40
      });

    expect(addedDocument).toEqual({
      storeId : "dummystoreid"
      , globalPercent : 40
      , badProp : 0
    });
  }
  catch (e) {
    console.log(e, `=====error test("creates new record"=====`);
  }
}, 100000);

I receive a long error when running jest. Thousands of rows show + 118, or similar number, along with object properties, all red text. Then a stack trace in white text.

// terminal 
      +                         116,
      +                         111,
      +                         51,

// continues for thousands of lines...


      +                       ],
      +                       "type": "Buffer",
      +                     },
      +                   ],
      +                   "format": "Protocol Buffer 3 DescriptorProto",
      +                   "type": Object {
      +                     "enumType": Array [],
      +                     "extension": Array [],
      +                     "extensionRange": Array [],
      +                     "field": Array [
      +                       Object {
      +                         "defaultValue": "",
      +                         "extendee": "",
      +                         "jsonName": "",
      +                         "label": "LABEL_OPTIONAL",
      +                         "name": "updateTime",
      +                         "number": 1,
      +                         "oneofIndex": 0,
      +                         "options": null,
      +                         "type": "TYPE_MESSAGE",
      +                         "typeName": "protobuf.Timestamp",
      +                       },
      +                       Object {
      +                         "defaultValue": "",
      +                         "extendee": "",
      +                         "jsonName": "",
      +                         "label": "LABEL_REPEATED",
      +                         "name": "transformResults",
      +                         "number": 2,
      +                         "oneofIndex": 0,
      +                         "options": null,
      +                         "type": "TYPE_MESSAGE",
      +                         "typeName": "Value",
      +                       },
      +                     ],
      +                     "name": "WriteResult",
      +                     "nestedType": Array [],
      +                     "oneofDecl": Array [],
      +                     "options": null,
      +                     "reservedName": Array [],
      +                     "reservedRange": Array [],
      +                   },
      +                 },
      +               },
      +             },
      +             "credentialsProvider": FirebaseCredentialsProvider {
      +               "auth": null,
      +               "changeListener": [Function anonymous],
      +               "currentUser": User {
      +                 "uid": null,
      +               },
      +               "forceRefresh": false,
      +               "receivedInitialUser": true,
      +               "tokenCounter": 1,
      +               "tokenListener": [Function anonymous],
      +             },
      +             "handshakeComplete_": true,
      +             "idleTimer": DelayedOperation {
      +               "asyncQueue": AsyncQueue {
      +                 "_isShuttingDown": false,
      +                 "delayedOperations": Array [
      +                   [Circular],
      +                 ],
      +                 "failure": null,
      +                 "operationInProgress": true,
      +                 "tail": Promise {},
      +                 "timerIdsToSkip": Array [],
      +               },
      +               "catch": [Function bound catch],
      +               "deferred": Deferred {
      +                 "promise": Promise {},
      +                 "reject": [Function anonymous],
      +                 "resolve": [Function anonymous],
      +               },
      +               "op": [Function anonymous],
      +               "removalCallback": [Function anonymous],
      +               "targetTimeMs": 1579867383588,
      +               "then": [Function bound then],
      +               "timerHandle": Timeout {
      +                 "_destroyed": false,
      +                 "_idleNext": TimersList {
      +                   "_idleNext": [Circular],
      +                   "_idlePrev": [Circular],
      +                   "expiry": 79350,
      +                   "id": -9007199254740987,
      +                   "msecs": 60000,
      +                   "priorityQueuePosition": 1,
      +                 },
      +                 "_idlePrev": TimersList {
      +                   "_idleNext": [Circular],
      +                   "_idlePrev": [Circular],
      +                   "expiry": 79350,
      +                   "id": -9007199254740987,
      +                   "msecs": 60000,
      +                   "priorityQueuePosition": 1,
      +                 },
      +                 "_idleStart": 19350,
      +                 "_idleTimeout": 60000,
      +                 "_onTimeout": [Function anonymous],
      +                 "_repeat": null,
      +                 "_timerArgs": undefined,
      +                 Symbol(refed): true,
      +                 Symbol(asyncId): 152,
      +                 Symbol(triggerId): 0,
      +               },
      +               "timerId": "write_stream_idle",
      +             },
      +             "idleTimerId": "write_stream_idle",
      +             "lastStreamToken": Object {
      +               "data": Array [
      +                 49,
      +               ],
      +               "type": "Buffer",
      +             },
      +             "listener": Object {
      +               "onClose": [Function bound ],
      +               "onHandshakeComplete": [Function bound ],
      +               "onMutationResult": [Function bound ],
      +               "onOpen": [Function bound ],
      +             },
      +             "queue": AsyncQueue {
      +               "_isShuttingDown": false,
      +               "delayedOperations": Array [
      +                 DelayedOperation {
      +                   "asyncQueue": [Circular],
      +                   "catch": [Function bound catch],
      +                   "deferred": Deferred {
      +                     "promise": Promise {},
      +                     "reject": [Function anonymous],
      +                     "resolve": [Function anonymous],
      +                   },
      +                   "op": [Function anonymous],
      +                   "removalCallback": [Function anonymous],
      +                   "targetTimeMs": 1579867383588,
      +                   "then": [Function bound then],
      +                   "timerHandle": Timeout {
      +                     "_destroyed": false,
      +                     "_idleNext": TimersList {
      +                       "_idleNext": [Circular],
      +                       "_idlePrev": [Circular],
      +                       "expiry": 79350,
      +                       "id": -9007199254740987,
      +                       "msecs": 60000,
      +                       "priorityQueuePosition": 1,
      +                     },
      +                     "_idlePrev": TimersList {
      +                       "_idleNext": [Circular],
      +                       "_idlePrev": [Circular],
      +                       "expiry": 79350,
      +                       "id": -9007199254740987,
      +                       "msecs": 60000,
      +                       "priorityQueuePosition": 1,
      +                     },
      +                     "_idleStart": 19350,
      +                     "_idleTimeout": 60000,
      +                     "_onTimeout": [Function anonymous],
      +                     "_repeat": null,
      +                     "_timerArgs": undefined,
      +                     Symbol(refed): true,
      +                     Symbol(asyncId): 152,
      +                     Symbol(triggerId): 0,
      +                   },
      +                   "timerId": "write_stream_idle",
      +                 },
      +               ],
      +               "failure": null,
      +               "operationInProgress": true,
      +               "tail": Promise {},
      +               "timerIdsToSkip": Array [],
      +             },
      +             "serializer": JsonProtoSerializer {
      +               "databaseId": DatabaseId {
      +                 "database": "(default)",
      +                 "projectId": "testproject",
      +               },
      +               "options": Object {
      +                 "useProto3Json": false,
      +               },
      +             },
      +             "state": 2,
      +             "stream": StreamBridge {
      +               "closeFn": [Function closeFn],
      +               "sendFn": [Function sendFn],
      +               "wrappedOnClose": [Function anonymous],
      +               "wrappedOnMessage": [Function anonymous],
      +               "wrappedOnOpen": [Function anonymous],
      +             },
      +           },
      +         },
      +         "sharedClientState": MemorySharedClientState {
      +           "localState": LocalClientState {
      +             "activeTargetIds": SortedSet {
      +               "comparator": [Function primitiveComparator],
      +               "data": SortedMap {
      +                 "comparator": [Function primitiveComparator],
      +                 "root": LLRBEmptyNode {
      +                   "size": 0,
      +                 },
      +               },
      +             },
      +           },
      +           "onlineStateHandler": [Function sharedClientStateOnlineStateChangedHandler],
      +           "queryState": Object {
      +             "2": "current",
      +           },
      +           "sequenceNumberHandler": null,
      +           "syncEngine": [Circular],
      +         },
      +         "syncEngineListener": EventManager {
      +           "onlineState": 1,
      +           "queries": ObjectMap {
      +             "inner": Object {},
      +             "mapKeyFn": [Function anonymous],
      +           },
      +           "snapshotsInSyncListeners": Set {},
      +           "syncEngine": [Circular],
      +         },
      +       },
      +     },
      +     "_persistenceKey": "app-1579867322226-0.11944467708511985",
      +     "_queue": AsyncQueue {
      +       "_isShuttingDown": false,
      +       "delayedOperations": Array [
      +         DelayedOperation {
      +           "asyncQueue": [Circular],
      +           "catch": [Function bound catch],
      +           "deferred": Deferred {
      +             "promise": Promise {},
      +             "reject": [Function anonymous],
      +             "resolve": [Function anonymous],
      +           },
      +           "op": [Function anonymous],
      +           "removalCallback": [Function anonymous],
      +           "targetTimeMs": 1579867383588,
      +           "then": [Function bound then],
      +           "timerHandle": Timeout {
      +             "_destroyed": false,
      +             "_idleNext": TimersList {
      +               "_idleNext": [Circular],
      +               "_idlePrev": [Circular],
      +               "expiry": 79350,
      +               "id": -9007199254740987,
      +               "msecs": 60000,
      +               "priorityQueuePosition": 1,
      +             },
      +             "_idlePrev": TimersList {
      +               "_idleNext": [Circular],
      +               "_idlePrev": [Circular],
      +               "expiry": 79350,
      +               "id": -9007199254740987,
      +               "msecs": 60000,
      +               "priorityQueuePosition": 1,
      +             },
      +             "_idleStart": 19350,
      +             "_idleTimeout": 60000,
      +             "_onTimeout": [Function anonymous],
      +             "_repeat": null,
      +             "_timerArgs": undefined,
      +             Symbol(refed): true,
      +             Symbol(asyncId): 152,
      +             Symbol(triggerId): 0,
      +           },
      +           "timerId": "write_stream_idle",
      +         },
      +       ],
      +       "failure": null,
      +       "operationInProgress": true,
      +       "tail": Promise {},
      +       "timerIdsToSkip": Array [],
      +     },
      +     "_settings": FirestoreSettings {
      +       "cacheSizeBytes": 41943040,
      +       "credentials": undefined,
      +       "forceLongPolling": false,
      +       "host": "localhost:8080",
      +       "ssl": false,
      +       "timestampsInSnapshots": true,
      +     },
      +   },
      +   "_fromCache": false,
      +   "_hasPendingWrites": false,
      +   "_key": DocumentKey {
      +     "path": ResourcePath {
      +       "len": 2,
      +       "offset": 0,
      +       "segments": Array [
      +         "globalRule",
      +         "YaGhEFEv3kFI0uUWWsSQ",
      +       ],
      +     },
      +   },
// text turns from red to white exactly here (including } )
        }
          at /home/owner/PhpstormProjects/shopify/buyUsedServer/functions/src/classes/__tests__/FirestoreConnection.test.ts:73:27
          at step (/home/owner/PhpstormProjects/shopify/buyUsedServer/functions/src/classes/__tests__/FirestoreConnection.test.ts:33:23)
          at Object.next (/home/owner/PhpstormProjects/shopify/buyUsedServer/functions/src/classes/__tests__/FirestoreConnection.test.ts:14:53)
          at fulfilled (/home/owner/PhpstormProjects/shopify/buyUsedServer/functions/src/classes/__tests__/FirestoreConnection.test.ts:5:58) {
        matcherResult: {
          actual: DocumentSnapshot {
            _firestore: [Firestore],
            _key: [DocumentKey],
            _document: [Document],
            _fromCache: false,
            _hasPendingWrites: false,
            _converter: undefined
          },
          expected: { storeId: 'dummystoreid', globalPercent: 40, badProp: 0 },
          message: [Function],
          name: 'toEqual',
          pass: false
        }
      } =====error test("creates new record"=====


Can anyone tell what is causing the catch to return an error here? The lack of error message is making this hard to debug for me.

1 Answer 1

2

A couple things stand out to me in your addDocument() method:

  1. You are first adding the document, then immediately performing a get() on the document. This will needlessly trigger a database read to pull down the same information you just provided.
  2. That get() returns a DocumentSnapshot which is a container object for a Firestore document value. Printing out its raw contents probably includes all kinds of things you don't want.

Your test isn't really testing any of your own logic, it's essentially testing the Firestore SDK (which is already quite well tested!). What you might want is to return a data object with the Firestore Document ID inserted into it. That might look something like this:

 async function addDocument(collectionName: string, documentData: object): Promise<object|null> {
    try {
      const newDocRef = await this.database
        .collection(collectionName)
        .add(documentData);

      return Object.assign({}, documentData, {
        __id__: newDocRef.id
      });
    }
    catch (e) {
      console.log(e, `=====error addDocument()=====`);
      return null;
    }
  }

Then you can write a test to make sure that the resulting object has a __id__ field set.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.