FIrestore: .add() возвращает массив с большими номерами

Я использую эмулятор 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 возвращать ошибку здесь? Отсутствие сообщения об ошибке затрудняет отладку для меня.


person Sean D    schedule 24.01.2020    source источник


Ответы (1)


В вашем методе addDocument() мне выделяется пара вещей:

  1. Сначала вы добавляете документ, а затем сразу же выполняете get() над документом. Это приведет к ненужному чтению базы данных, чтобы получить ту же информацию, которую вы только что предоставили.
  2. Этот get() возвращает DocumentSnapshot, который является объектом-контейнером для Значение документа Firestore. Распечатка его необработанного содержимого, вероятно, включает в себя все, что вам не нужно.

Ваш тест на самом деле не проверяет вашу собственную логику, он, по сути, тестирует SDK Firestore (который уже достаточно хорошо протестирован!). Возможно, вы захотите вернуть объект данных со вставленным в него идентификатором документа Firestore. Это может выглядеть примерно так:

 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__.

person Michael Bleigh    schedule 24.01.2020