在 Jest 中模拟 Firebase 管理员时出错:“TypeError:admin.firestore 不是函数”
Posted
技术标签:
【中文标题】在 Jest 中模拟 Firebase 管理员时出错:“TypeError:admin.firestore 不是函数”【英文标题】:Error Mocking Firebase Admin in Jest: "TypeError: admin.firestore is not a function" 【发布时间】:2020-08-24 11:07:21 【问题描述】:我有一个函数来处理通过 Admin SDK 连接到 Cloud Firestore。我知道该功能工作正常,因为应用程序连接并允许写入数据库。
现在我正在尝试用 Jest 测试这个功能。为避免在此功能范围之外进行测试,我正在模拟 firebase-admin 节点模块。但是,我的测试失败并出现错误“TypeError: admin.firestore is not a function”。
我的函数和测试都是用 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)
)
)
这是我的相关(或可能相关)package.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,
【问题讨论】:
【参考方案1】:您的代码看起来不错。 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
属性添加一个模拟:
// /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
方法的值,您还可以定义该属性,以便它返回一个模拟函数。
为了简化模拟,我会在你的测试文件中创建一个小助手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)
)
)
【讨论】:
我尝试了这个解决方案,但 TypeScript 抱怨admin.firestore = jest.fn()
“不能分配给 'firestore',因为它是一个只读属性。” ESLint 的 Jest 插件随后将其自动修复为 jest.spyOn(admin, "firestore").mockImplementation()
,但随后我的测试失败并出现“错误:无法监视 firestore 属性,因为它不是函数;改为未定义”。试图监视“firestore()”会给我一个 TS 错误“没有重载匹配这个调用。”
好吧,事情变得越来越棘手。如果jest.spyOn(admin, 'firestore', 'get').mockImplementation(() => jest.fn())
适合你,你可以试试吗?
TS 为“firestore”抛出错误:“没有重载匹配此调用。重载 1 of 4, '(object: typeof import("c:/Development/CephalonTobran/CephalonTobranBackend/node_modules/firebase- admin/lib/index.d.ts"), method: "SDK_VERSION" | "apps" | "credential", accessType: "get"): SpyInstance<...>', 给出以下错误。类型参数 ' “firestore”不能分配给“SDK_VERSION”|“apps”|“credential”'类型的参数。“它继续,但我没有足够的空间来处理完整的错误。
嗨@EdRands,我添加了一个替代解决方案。希望对您有所帮助!
这就是票!哇!谢谢。【参考方案2】:
当您最初模拟它时,将您自己的模拟与其自动模拟结合起来可以正常工作,即:
jest.mock('firebase-admin', () => (
...jest.mock('firebase-admin'),
credential:
cert: jest.fn(),
,
initializeApp: jest.fn(),
firestore: jest.fn(),
));
【讨论】:
以上是关于在 Jest 中模拟 Firebase 管理员时出错:“TypeError:admin.firestore 不是函数”的主要内容,如果未能解决你的问题,请参考以下文章
开玩笑的测试在调用模拟的 firebase 函数之前完成,因此失败
Supertest 在 Node 中进行 Jest 测试时出现 MaxListenersExceedingWarning 错误