Модульное тестирование Firebase Firestore и облачных функций

Мой личный проект заставил меня открыть ящик Пандоры со всеми развлечениями и новыми технологиями. Обычно я не могу использовать в своей дневной работе — Firebase Firestore и Cloud Functions (Lambdas для вас, ребята из AWS). 🤖

Я поставил перед собой задачу написать функцию, которая берет полезную нагрузку данных и создает запись в Firebase. Наряду с этой проблемой я хотел обернуть свою функциональность модульными тестами в качестве новой цели.

Официальный Документация по облачным функциям Firebase легко читается и понимается для очень простых случаев использования. Я хотел пройти лишнюю милю за пределами основных примеров. 😄

Здесь у меня есть простая функция, которая прослушивает событие создания документа Firestore. Он вызовет облачную функцию, чтобы взять данные, проверить, существуют ли они, и если нет, создать соответствующую запись.

const functions = require('firebase-functions');
const admin = require('firebase-admin');

admin.initializeApp();
let db = admin.firestore();

exports.onEpisodeTrackCreated = functions.firestore.document('episodes/{episodeId}/tracks/{trackIndex}')
  .onCreate((snap, context) => {
    const data = snap.data()

    if (!data.name) throw new Error('Missing `name` parameter')

    const name = data.name.trim()
    let tracksRef = db.collection('tracks')

    return tracksRef.where('name', '==', name).get()
      .then(snapshot => {
        if (snapshot.empty) {
          return tracksRef.add({
            name: name
          })
        }
        let doc
        snapshot.forEach(snapDoc => {
          doc = snapDoc
        })
        return doc
      })
        .then((doc) => {
          snap.ref.set({
            trackId: doc.id
          }, { merge: true })
          return doc
        })
  })

Установить firebase-functions-test и Шутка; популярная среда тестирования с включенными батареями.

npm install --save-dev firebase-functions-test jest

Нам нужно будет создать test папку, где мы будем хранить юнит-тесты для наших функций.

Далее я обновил package.json с тестовым сценарием для вызова.

«скрипты»: {
«тест»: «это тест /»
}

Облачные функции Firebase могут работать в режимах онлайн и офлайн. Онлайн-режим означает, что он будет взаимодействовать с вашей учетной записью Firebase, создавать/удалять данные. Автономный режим приведет к тому, что мы будем прерывать наши звонки, и, на мой взгляд, это предпочтительный вариант для написания этой статьи.

Инициализируйте SDK в автономном режиме, не определяя никаких параметров конфигурации.

const test = require('firebase-functions-test')();

Давайте продолжим написание нашего модульного теста, который вызывает функцию и должен успешно разрешаться с помощью async/await, иначе он выдаст ошибку.

const test = require('firebase-functions-test')();
const functions = require('../index.js');

describe('onEpisodeTrackCreated', () => {
  it('successfully invokes function', async () => {
    const wrapped = test.wrap(functions.onEpisodeTrackCreated);
    const data = { name: 'hello - world', broadcastAt: new Date() }
    await wrapped({
      data: () => ({
        name: 'hello - world'
      }),
      ref:{
        set: jest.fn()
      }
    })
  })
})

Что произойдет, если мы запустим тест сейчас? 🤔


 FAIL  tests/index.test.js
  onEpisodeTrackCreated
    ✕ successfully invokes function (832ms)

  ● onEpisodeTrackCreated › successfully invokes function

    Could not load the default credentials. Browse to 
cation/getting-started for more information.

      at GoogleAuth.getApplicationDefaultAsync (node_modules/google-auth-library/build/src/auth/googleauth.js:161:19)
      at GoogleAuth.getClient (node_modules/google-auth-library/build/src/auth/googleauth.js:503:17)
      at GrpcClient._getCredentials (node_modules/google-gax/src/grpc.ts:150:20)
      at GrpcClient.createStub (node_modules/google-gax/src/grpc.ts:295:19)

Test Suites: 1 failed, 1 total
Tests:       1 failed, 1 total
Snapshots:   0 total
Time:        1.91s

😢 это нехорошо.

Если подумать об этой ошибке немного подробнее, в нашем коде действительно происходит довольно много. Эта ошибка говорит нам что-то об учетных данных. Возможно, это связано с initializeApp на firebase-admin? 🤔

Мы будем издеваться над этим и посмотрим, что будет дальше.

jest.mock('firebase-admin', () => ({
  initializeApp: jest.fn()
}))

И результат…

 FAIL  tests/index.test.js
  ● Test suite failed to run

    TypeError: admin.firestore is not a function

      3 | 
      4 | admin.initializeApp();
    > 5 | let db = admin.firestore();
        |                ^
      6 | 
      7 | exports.onEpisodeTrackCreated = functions.firestore.document('episodes/{episodeId}/tracks/{trackIndex}')
      8 |   .onCreate((snap, context) => {

      at Object.firestore (index.js:5:16)
      at Object.require (tests/index.test.js:23:19)

Test Suites: 1 failed, 1 total
Tests:       0 total
Snapshots:   0 total
Time:        1.757s

Великолепно, это лучшая позиция. Потому что мы взываем к firestore но мы полностью издевались над реализацией, как и ожидалось.

Теперь, чтобы завершить насмешку для этого теста. 😅

const mockQueryResponse = jest.fn()
mockQueryResponse.mockResolvedValue([
  {
    id: 1
  }
])

jest.mock('firebase-admin', () => ({
  initializeApp: jest.fn(),
  firestore: () => ({
   collection: jest.fn(path => ({
     where: jest.fn(queryString => ({
       get: mockQueryResponse
     }))
   })) 
  })
}))

И последний заезд. 😬

 PASS  tests/index.test.js
  onEpisodeTrackCreated
    ✓ successfully invokes function (3ms)

Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   0 total
Time:        1.026s

Блестящий. 🙌

Я очень надеюсь, что это решение поможет вам в тестировании вашего следующего проекта.

Источники

Конечно, этот результат не возник сам собой, потребовался долгий поиск в Интернете соответствующих решений на этапе кодирования.

Похожие записи

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *