开玩笑中嘲笑Firebase管理员时出错:"TypeError: admin.firestore is not a function"



我有一个函数可以处理通过Admin SDK连接到Cloud Firestore的问题。我知道这个功能运行得很好,因为应用程序连接并允许写入数据库。

现在我正在尝试用Jest测试这个功能。为了避免在这个函数的范围之外进行测试,我嘲笑firebase管理节点模块。然而,我的测试失败了,错误为"TypeError:admin.firestore不是函数"。

我的函数和测试都是用TypeScript编写的,通过ts jest运行,但我不认为这是TypeScript错误,因为VS Code没有任何抱怨。我相信这是Jest自动嘲讽的问题。

admin.firebase()是一个有效的调用。TypeScript定义文件将其定义为function firestore(app?: admin.app.App): admin.firestore.Firestore;

我已经阅读了Jest文档,但我不知道如何解决这个问题。

这是我的功能:

// /src/lib/database.ts
import * as admin from "firebase-admin"
/**
* Connect to the database
* @param key - a base64 encoded JSON string of serviceAccountKey.json
* @returns - a Cloud Firestore database connection
*/
export function connectToDatabase(key: string): FirebaseFirestore.Firestore {
// irrelevant code to convert the key
try {
admin.initializeApp({
credential: admin.credential.cert(key),
})
} catch (error) {
throw new Error(`Firebase initialization failed. ${error.message}`)
}
return admin.firestore() // this is where it throws the error
}

这是我的测试代码:

// /tests/lib/database.spec.ts
jest.mock("firebase-admin")
import * as admin from "firebase-admin"
import { connectToDatabase } from "@/lib/database"
describe("database connector", () => {
it("should connect to Firebase when given valid credentials", () => {
const key = "ewogICJkdW1teSI6ICJUaGlzIGlzIGp1c3QgYSBkdW1teSBKU09OIG9iamVjdCIKfQo=" // dummy key
connectToDatabase(key) // test fails here
expect(admin.initializeApp).toHaveBeenCalledTimes(1)
expect(admin.credential.cert).toHaveBeenCalledTimes(1)
expect(admin.firestore()).toHaveBeenCalledTimes(1)
})
})

以下是我的相关(或可能相关(软件包.json(与Yarn v1一起安装(:

{
"dependencies": {
"@firebase/app-types": "^0.6.0",
"@types/node": "^13.13.5",
"firebase-admin": "^8.12.0",
"typescript": "^3.8.3"
},
"devDependencies": {
"@types/jest": "^25.2.1",
"expect-more-jest": "^4.0.2",
"jest": "^25.5.4",
"jest-chain": "^1.1.5",
"jest-extended": "^0.11.5",
"jest-junit": "^10.0.0",
"ts-jest": "^25.5.0"
}
}

我的笑话配置:

// /jest.config.js
module.exports = {
setupFilesAfterEnv: ["jest-extended", "expect-more-jest", "jest-chain"],
preset: "ts-jest",
errorOnDeprecated: true,
testEnvironment: "node",
moduleNameMapper: {
"^@/(.*)$": "<rootDir>/src/$1",
},
moduleFileExtensions: ["ts", "js", "json"],
testMatch: ["<rootDir>/tests/**/*.(test|spec).(ts|js)"],
clearMocks: true,
}

您的代码看起来不错。jest.mock模拟库的所有方法,默认情况下,所有方法在调用时都将返回undefined

解释

您看到的问题与如何定义firebase-admin模块方法有关。

firebase-admin包的源代码中,initializeApp方法被定义为FirebaseNamespace.prototype:中的方法

FirebaseNamespace.prototype.initializeApp = function (options, appName) {
return this.INTERNAL.initializeApp(options, appName);
};

然而,firestore方法被定义为一个属性:

Object.defineProperty(FirebaseNamespace.prototype, "firestore", {
get: function () {
[...]
return fn;
},
enumerable: true,
configurable: true
});

jest.mock似乎能够模拟直接在prototype中声明的方法(这就是为什么对admin.initializeApp的调用不会抛出错误的原因(,但不能模拟定义为属性的方法。

解决方案

为了克服这个问题,您可以在运行测试之前为firestore属性添加一个mock:

// /tests/lib/database.spec.ts
import * as admin from "firebase-admin"
import { connectToDatabase } from "@/lib/database"
jest.mock("firebase-admin")
describe("database connector", () => {
beforeEach(() => {
// Complete firebase-admin mocks
admin.firestore = jest.fn()
})
it("should connect to Firebase when given valid credentials", () => {
const key = "ewogICJkdW1teSI6ICJUaGlzIGlzIGp1c3QgYSBkdW1teSBKU09OIG9iamVjdCIKfQo=" // dummy key
connectToDatabase(key) // test fails here
expect(admin.initializeApp).toHaveBeenCalledTimes(1)
expect(admin.credential.cert).toHaveBeenCalledTimes(1)
expect(admin.firestore).toHaveBeenCalledTimes(1)
})
})

替代解决方案

由于以前的解决方案对您不起作用,我将建议另一种解决方案。您可以定义属性,使其返回一个模拟函数,而不是分配firestore方法的值。

为了简化mock,我将在您的测试文件中创建一个小助手mockFirestoreProperty

// /tests/lib/database.spec.ts
import * as admin from "firebase-admin"
import { connectToDatabase } from "@/lib/database"
jest.mock("firebase-admin")
describe("database connector", () => {
// This is the helper. It creates a mock function and returns it
// when the firestore property is accessed.
const mockFirestoreProperty = admin => {
const firestore = jest.fn();
Object.defineProperty(admin, 'firestore', {
get: jest.fn(() => firestore),
configurable: true
});
};
beforeEach(() => {
// Complete firebase-admin mocks
mockFirestoreProperty(admin);
})
it("should connect to Firebase when given valid credentials", () => {
const key = "ewogICJkdW1teSI6ICJUaGlzIGlzIGp1c3QgYSBkdW1teSBKU09OIG9iamVjdCIKfQo=" // dummy key
connectToDatabase(key) // test fails here
expect(admin.initializeApp).toHaveBeenCalledTimes(1)
expect(admin.credential.cert).toHaveBeenCalledTimes(1)
expect(admin.firestore).toHaveBeenCalledTimes(1)
})
})

当你最初模拟它时,它可以正确地将你自己的模拟与它的自动模拟相结合,即:

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

最新更新