Модульное тестирование 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
Блестящий. 🙌
Я очень надеюсь, что это решение поможет вам в тестировании вашего следующего проекта.
Источники
Конечно, этот результат не возник сам собой, потребовался долгий поиск в Интернете соответствующих решений на этапе кодирования.