当测试套件中 运行 时,云函数中事务中的数据始终为空
Data in transaction in a cloud function is always null when run in test suite
我有一个 Firebase 云函数可以执行此操作:
const admin = require('firebase-admin')
admin.initializeApp()
exports.setSessionState = functions.https.onCall(async (data, context) => {
const stateId = data.stateId
const details = data.details
const stateRef = admin.database().ref(`state/${stateId}`)
stateRef.transaction((state) => {
if (state) {
state.details = details
}
return state
})
})
该代码在实际应用程序中运行良好并且状态已更新,但我 运行 在测试中 运行 遇到问题,状态始终为 null
。 (换句话说,永远不会设置细节。)该测试使用 Mocha 框架,并且 运行 针对真实的 Firebase 项目,我看不出这里有什么不同,但是调用它时行为始终不同来自客户端应用程序的云功能以及从测试套件调用它时:
const chai = require('chai')
const assert = chai.assert
const test = require('firebase-functions-test')({
databaseURL: '<redacted>',
storageBucket: '<redacted>',
projectId: '<redacted>',
}, '../service-account-credentials.json')
describe('CFsetSessionState', () => {
let wrapped
let cloudFunctions
let admin
before(() => {
cloudFunctions = require('../index')
admin = require('firebase-admin')
wrapped = test.wrap(cloudFunctions.CFsetSessionState)
})
it('Test setting state', async () => {
const stateId = 'abc'
const data = {
stateId: stateId,
details: {
name: 'New name'
}
}
const context = {
auth: {
uid: 123
}
}
const stateRef = admin.database().ref(`state/${stateId}`)
await stateRef.set({
name: 'Previous name',
active: true
})
await wrapped(data, context)
const snapshot = await stateRef.once('value')
const state = snapshot.val()
// Details do not exist here, why?
assert.equal(state.details.name, 'New name')
})
})
我在测试后保持数据库状态不变,所以我可以看到状态对象中确实有数据,但尚未设置详细信息。在调用云函数之前和之后尝试设置和获取数据(使用 .once()
)可以改变行为,让我觉得这可能是一些缓存问题,但这个实验并没有给我任何特别稳定的结束状态。我不知道本地缓存的等价物在云函数中是如何工作的,它目前显示随机行为。
这可能是什么原因造成的?
我没有尝试 运行 你的 Cloud Function 测试,但问题很可能是因为你错误地管理了 Cloud Function 的生命周期。由于它是可调用的,因此您需要通过返回一个 Promise(或所有异步工作完成时的值)来终止它。文档中有更多详细信息 here and here。
因此,您应该按如下方式调整您的 CF:
exports.setSessionState = functions.https.onCall(async (data, context) => {
const stateId = data.stateId
const details = data.details
const stateRef = admin.database().ref(`state/${stateId}`)
return stateRef.transaction((state) => { // Note the return here
if (state) {
state.details = details
}
return state
})
})
我们正在返回 Promise returned by the Transaction。
我有一个 Firebase 云函数可以执行此操作:
const admin = require('firebase-admin')
admin.initializeApp()
exports.setSessionState = functions.https.onCall(async (data, context) => {
const stateId = data.stateId
const details = data.details
const stateRef = admin.database().ref(`state/${stateId}`)
stateRef.transaction((state) => {
if (state) {
state.details = details
}
return state
})
})
该代码在实际应用程序中运行良好并且状态已更新,但我 运行 在测试中 运行 遇到问题,状态始终为 null
。 (换句话说,永远不会设置细节。)该测试使用 Mocha 框架,并且 运行 针对真实的 Firebase 项目,我看不出这里有什么不同,但是调用它时行为始终不同来自客户端应用程序的云功能以及从测试套件调用它时:
const chai = require('chai')
const assert = chai.assert
const test = require('firebase-functions-test')({
databaseURL: '<redacted>',
storageBucket: '<redacted>',
projectId: '<redacted>',
}, '../service-account-credentials.json')
describe('CFsetSessionState', () => {
let wrapped
let cloudFunctions
let admin
before(() => {
cloudFunctions = require('../index')
admin = require('firebase-admin')
wrapped = test.wrap(cloudFunctions.CFsetSessionState)
})
it('Test setting state', async () => {
const stateId = 'abc'
const data = {
stateId: stateId,
details: {
name: 'New name'
}
}
const context = {
auth: {
uid: 123
}
}
const stateRef = admin.database().ref(`state/${stateId}`)
await stateRef.set({
name: 'Previous name',
active: true
})
await wrapped(data, context)
const snapshot = await stateRef.once('value')
const state = snapshot.val()
// Details do not exist here, why?
assert.equal(state.details.name, 'New name')
})
})
我在测试后保持数据库状态不变,所以我可以看到状态对象中确实有数据,但尚未设置详细信息。在调用云函数之前和之后尝试设置和获取数据(使用 .once()
)可以改变行为,让我觉得这可能是一些缓存问题,但这个实验并没有给我任何特别稳定的结束状态。我不知道本地缓存的等价物在云函数中是如何工作的,它目前显示随机行为。
这可能是什么原因造成的?
我没有尝试 运行 你的 Cloud Function 测试,但问题很可能是因为你错误地管理了 Cloud Function 的生命周期。由于它是可调用的,因此您需要通过返回一个 Promise(或所有异步工作完成时的值)来终止它。文档中有更多详细信息 here and here。
因此,您应该按如下方式调整您的 CF:
exports.setSessionState = functions.https.onCall(async (data, context) => {
const stateId = data.stateId
const details = data.details
const stateRef = admin.database().ref(`state/${stateId}`)
return stateRef.transaction((state) => { // Note the return here
if (state) {
state.details = details
}
return state
})
})
我们正在返回 Promise returned by the Transaction。