FIrestore: .add() returns 大数组

FIrestore: .add() returns large numbered array

我正在使用 firebase-tools 模拟器在本地测试将记录保存到 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

我的代码:

// 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);

我在 运行 jest 时收到一个长错误。数千行显示 + 118, 或类似数字,以及对象属性,全部为红色文本。然后是白色文本的堆栈跟踪。

// 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"=====


谁能告诉我是什么导致 catch 在这里 return 出错?缺少错误消息让我很难调试。

你的 addDocument() 方法中有几件事让我印象深刻:

  1. 您首先添加文档,然后立即对文档执行 get()。这将不必要地触发数据库读取以提取您刚刚提供的相同信息。
  2. get() return 是一个 DocumentSnapshot,它是 Firestore 文档值的容器对象。打印出它的原始内容可能包括各种你不想要的东西。

您的测试并没有真正测试您自己的任何逻辑,它实际上是在测试 Firestore SDK(已经过良好测试!)。您可能需要 return 一个数据对象,其中插入了 Firestore 文档 ID。这可能看起来像这样:

 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;
    }
  }

然后您可以编写测试以确保生成的对象具有 __id__ 字段集。