Jest 与 MongoDB 集成指南:基于 jest-mongodb Preset 与 Global Setup/Teardown 的数据库测试方案
【免费下载链接】jestDelightful JavaScript Testing.项目地址: https://gitcode.com/gh_mirrors/je/jest
本指南面向需要在 Jest 测试套件中真实读写 MongoDB 的开发者。借助 Jest 的 Global Setup/Teardown 与 Async Test Environment 能力,配合
@shelf/jest-mongodb预设,你可以在一套测试进程中自动拉起内存版 MongoDB 实例、完成数据读写断言并在收尾时自动清理,全程无需手工维护数据库服务进程。读完本文,你将掌握 preset 的一键接入方式、测试代码的编写规范,以及该方案底层在 Jest 源码中的执行原理。
为什么测试 MongoDB 代码需要专门方案
单元测试通常主张隔离外部依赖,但涉及数据库访问逻辑(如仓储层、DAO、聚合查询)时,仅靠 mock 驱动会损失对真实查询语义的验证能力。Jest 对这类场景的官方建议是:在测试进程的全局生命周期中启动一个真实的 MongoDB 实例,让所有测试文件共享连接,跑完再统一关闭。这样做的好处是:
- 测试之间天然共享同一个数据库实例,无需每个文件各自起服务;
- 数据库状态由全局 Setup/Teardown 管理,测试文件内部只需关注读写与断言;
- 配合 preset 的默认配置,无需手动引入任何连接管理依赖。
底层机制:Jest 的 Global Setup/Teardown 与异步测试环境
该方案能成立,依赖两个 Jest 配置能力,它们在本仓库的版本化文档 Configuration.md 中有完整定义:
globalSetup(string,默认undefined):指向一个自定义全局 Setup 模块,该模块必须导出一个函数(同步或异步均可),Jest 会在所有测试文件执行前调用一次,并传入globalConfig与projectConfig两个参数。globalTeardown(string,默认undefined):与globalSetup对称,在所有测试文件执行完毕后调用一次,同样接收globalConfig与projectConfig,适合用来关闭数据库进程、清理临时资源。testEnvironment(node|jsdom| string,默认node):指定测试运行环境;MongoDB 相关预设会通过自定义环境在globalThis上注入连接信息。
从本仓库源码可以确认这套生命周期的实际调度位置:在 packages/jest-core/src/runJest.ts#L397-L401 中,runGlobalHook({allTests, globalConfig, moduleName: 'globalSetup'})在测试调度(scheduler.scheduleTests)之前被await;而 packages/jest-core/src/runJest.ts#L438-L442 则在所有测试跑完之后执行globalTeardown钩子。换句话说:先启动数据库 → 跑全部测试 → 再关闭数据库,这正是 MongoDB 集成方案的执行模型。
再看 packages/jest-core/src/runGlobalHook.ts,其中值得注意的实现细节有:
- 钩子模块路径取自各测试上下文配置(
test.context.config[moduleName]),支持多项目场景; - 通过
createScriptTransformer与requireAndTranspileModule加载并转译钩子文件,随后以globalModule(globalConfig, projectConfig)方式调用; - 若模块导出的不是函数,会抛出
TypeError: <moduleName> file must export a function at <modulePath>。
文档还特别提醒了两个使用边界:
globalSetup中定义的全局变量只能在globalTeardown中读取,无法在测试套件里直接访问——这正是 preset 方案需要把连接信息注入globalThis(如__MONGO_URI__)而不是依赖普通globals配置的原因;- Jest 不会对
node_modules内的代码做转换,因为加载转码器本身需要真实模块。
另外,若在globals配置项中放全局值,要求其必须可被 JSON 序列化(见 Configuration.md#globals-object),且无法用于定义函数,因此像 MongoDB 连接这种运行时对象只能通过 Setup 阶段写入globalThis完成传递。
快速开始:安装并接入 jest-mongodb Preset
Jest MongoDB(npm 包名@shelf/jest-mongodb)提供了运行 MongoDB 测试所需的全部配置:自动下载/启动 mongod、注入连接 URI 与库名、测试结束后回收进程。接入只需三步。
第一步:安装预设
npm install --save-dev @shelf/jest-mongodb第二步:在 Jest 配置中声明 preset
在package.json的jest字段或jest.config.js/jest.config.ts中指定:
{ "preset": "@shelf/jest-mongodb" }用配置文件写法同样可以:
// jest.config.js const {defineConfig} = require('jest'); module.exports = defineConfig({ preset: '@shelf/jest-mongodb', });第三步:编写测试
const {MongoClient} = require('mongodb'); describe('insert', () => { let connection; let db; beforeAll(async () => { connection = await MongoClient.connect(globalThis.__MONGO_URI__, { useNewUrlParser: true, useUnifiedTopology: true, }); db = await connection.db(globalThis.__MONGO_DB_NAME__); }); afterAll(async () => { await connection.close(); }); it('should insert a doc into collection', async () => { const users = db.collection('users'); const mockUser = {_id: 'some-user-id', name: 'John'}; await users.insertOne(mockUser); const insertedUser = await users.findOne({_id: 'some-user-id'}); expect(insertedUser).toEqual(mockUser); }); });这段测试的核心要点:
globalThis.__MONGO_URI__:由 preset 在测试环境初始化阶段注入的 MongoDB 连接串;globalThis.__MONGO_DB_NAME__:preset 分配好的数据库名,避免各测试文件相互污染;- 连接在
beforeAll中建立、在afterAll中关闭,测试体内只做业务断言; - 无需加载任何额外依赖——不需要你自行引入 mongodb-memory-server 之类的包,preset 已把连接生命周期管理好。
Preset 的解析与合并机制(源码视角)
你可能好奇"preset": "@shelf/jest-mongodb"这一行配置背后发生了什么。在 packages/jest-config/src/normalize.ts#L171-L259 的setupPreset函数中,Jest 会:
- 以
jest-preset为文件名(PRESET_NAME),按.json/.js/.cjs/.mjs扩展名依次解析 preset 模块(对应 normalize.ts#L64 与 normalize.ts#L177-L185); - 对多项目场景强制清除模块缓存(
delete require.cache[...]),保证每个项目拿到独立配置; - 将 preset 中的
setupFiles、setupFilesAfterEnv、moduleNameMapper、transform、globals等项与用户配置按特定顺序合并(用户显式配置优先),最后返回{...preset, ...options}。
这也是官方文档 Configuration.md#preset-string 所描述的约定:preset 必须指向一个在根目录包含jest-preset.json、jest-preset.js、jest-preset.cjs或jest-preset.mjs文件的 npm 模块,也可以直接使用相对文件系统路径。@shelf/jest-mongodb正是通过这一机制,把启动/注入 mongod 的配置作为一份可复用的基线配置注入你的测试流程。
不依赖 preset 的手动实现:Global Setup 示例
如果你不想引入第三方 preset(例如需要固定 MongoDB 版本、多环境切换或已有 mongod 管理脚本),可以基于globalSetup/globalTeardown手写等效逻辑。下面的示例摘自本仓库的 Configuration.md#globalsetup-string:
module.exports = async function (globalConfig, projectConfig) { console.log(globalConfig.testPathPatterns); console.log(projectConfig.cache); // Set reference to mongod in order to close the server during teardown. globalThis.__MONGOD__ = mongod; };module.exports = async function (globalConfig, projectConfig) { console.log(globalConfig.testPathPatterns); console.log(projectConfig.cache); await globalThis.__MONGOD__.stop(); };然后通过配置把它们挂到生命周期上:
module.exports = { globalSetup: '<rootDir>/setup.js', globalTeardown: '<rootDir>/teardown.js', testEnvironment: 'node', };这套手动方案与 preset 方案的差异点在于:preset 帮你完成了 mongod 的下载、启动、URI/库名注入等重复劳动;而手动方案给你完全的控制权,代价是需要自己负责进程管理与全局变量注入。无论哪种方式,都必须遵守文档中的边界:Setup 中写入globalThis的对象只能由 Teardown 读取,测试文件里拿到的连接信息来自测试环境而非 Setup 的全局变量。
注意事项与常见问题
结合原文档与仓库实现,使用该方案时有几点需要特别留意:
useNewUrlParser/useUnifiedTopology选项:原文档示例中显式传入这两个选项,它们分别对应 MongoDB Node.js Driver 的连接解析器与拓扑监控开关;若你使用的 driver 版本已默认启用,可省略。请以实际安装的mongodb驱动版本说明为准。- 全局变量不可跨测试读取:
globalSetup里定义的变量无法在测试套件中访问,只能在globalTeardown中读取;连接信息必须走testEnvironment注入globalThis的通道。 globals配置须可 JSON 序列化:不要在globals中存放连接对象或函数,应使用setupFiles或 preset 提供的注入机制。- 多项目 runner 的触发条件:某个项目中配置的 global setup/teardown 只在至少运行该项目的一个测试时才会触发。
node_modules不参与转换:hook 文件的加载虽经转换器处理,但node_modules内的代码不会被 Jest 转换,涉及 Babel/TypeScript 的依赖请预编译。
小结
Jest 对 MongoDB 的支持并非特殊内建功能,而是globalSetup/globalTeardown与自定义测试环境能力的自然延伸:安装@shelf/jest-mongodb、声明 preset、按__MONGO_URI__/__MONGO_DB_NAME__编写测试即可获得开箱即用的真实数据库测试体验;而源码中 runGlobalHook.ts 与 normalize.ts 的实现则解释了生命周期调度与预设合并的底层原理。本仓库的 e2e/global-setup 与 e2e/tests/globalSetup.test.ts 也提供了全局 Setup/Teardown 的端到端测试用例,可作为深入理解该机制的参考样本。对于版本选择、MongoDB 二进制版本固定等进阶配置,请查阅 jest-mongodb 自身的项目文档。
【免费下载链接】jestDelightful JavaScript Testing.项目地址: https://gitcode.com/gh_mirrors/je/jest
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考