Jest能否仅mock外部模块指定部分,而非完整mock整个依赖库?
Jest 部分 mock 模块的实现方案
完全可以实现只 mock 模块的部分导出,不需要全量替换整个依赖库的实现。结合你的场景,有两种适配度最高的方案,可根据需求选择。
方案一(改动最小,推荐):使用 jest.spyOn 直接替换目标方法
这种方式不需要编写全量的模块 mock 逻辑,只需要针对DatabaseFacade的get方法做替换,其他所有框架导出的内容(DatabaseModule、Pg等)完全保留真实实现,和你现有代码的兼容性最高。
改动代码
只需要在原有测试用例的beforeAll和afterAll里补充两行spy逻辑即可,其余代码完全不用改动:
import { DatabaseModule, Pg, DatabaseFacade } from '@organisation/framework' import { StationRepository } from './StationRepository' import { DatabaseConfig } from '../../spec' const DatabaseInstance = new DatabaseModule() beforeAll(async () => { DatabaseInstance.add('default', new Pg('default', DatabaseConfig)) // 替换DatabaseFacade的get方法,让它返回你自定义创建的数据库实例 jest.spyOn(DatabaseFacade, 'get').mockImplementation(() => DatabaseInstance) jest.clearAllMocks() }) afterAll(async () => { // 测试完成后恢复原方法,避免影响其他用例 jest.restoreAllMocks() const database = DatabaseInstance.get() await database.disconnect() }) // 你原本的测试用例完全复用,不需要修改 describe('find timezone', () => { it('can find a timezone', async () => { const ids = [1, 2] const expected = ['Pacific/Auckland', 'Australia/Adelaide'] const results = [] for (const id of ids) { const timezone: string | null = await StationRepository.findTimezone(id) results.push(timezone) } expect(results).toEqual(expected) }) })
可选扩展:纯单元测试不连真实数据库
如果不想依赖真实的数据库连接,可以进一步mockqueryOne方法的返回值,完全脱离数据库环境:
beforeAll(async () => { DatabaseInstance.add('default', new Pg('default', DatabaseConfig)) jest.spyOn(DatabaseFacade, 'get').mockImplementation(() => DatabaseInstance) // 直接mock查询方法的返回值,不需要真实查库 jest.spyOn(DatabaseInstance, 'queryOne').mockImplementation(async (_query, params) => { const stationId = params[0] if (stationId === 1) return { timezone: 'Pacific/Auckland' } if (stationId === 2) return { timezone: 'Australia/Adelaide' } return null }) })
方案二:部分 mock 整个模块
如果你需要更彻底地控制模块的导出内容,可以使用Jest原生的部分mock能力,逻辑是先加载模块的所有真实实现,仅覆盖需要替换的导出项:
// 注意:jest.mock会被Jest自动提升到文件最顶部执行 jest.mock('@organisation/framework', () => { // 拿到模块的所有真实导出 const actualModule = jest.requireActual('@organisation/framework') // 自定义要替换的DatabaseFacade实现 const mockDatabaseFacade = { get: jest.fn() } return { // 保留所有原模块的真实导出 ...actualModule, // 仅覆盖需要替换的DatabaseFacade DatabaseFacade: mockDatabaseFacade } }) // 正常导入依赖 import { DatabaseModule, Pg, DatabaseFacade } from '@organisation/framework' import { StationRepository } from './StationRepository' import { DatabaseConfig } from '../../spec' const DatabaseInstance = new DatabaseModule() beforeAll(async () => { DatabaseInstance.add('default', new Pg('default', DatabaseConfig)) // 给mock的get方法绑定返回值 DatabaseFacade.get.mockImplementation(() => DatabaseInstance) jest.clearAllMocks() }) afterAll(async () => { jest.restoreAllMocks() const database = DatabaseInstance.get() await database.disconnect() })
这种方式的效果和方案一完全一致,适合需要一次性替换多个模块导出的场景。
注意事项
- 不要全量mock整个
@organisation/framework模块,否则你之前已经跑通的数据库连接创建逻辑会全部失效,这也是你之前遇到的问题的核心原因 - 所有mock的方法/实例,要在测试结束后通过
jest.restoreAllMocks或者mockClear做清理,避免影响其他测试用例的运行 - 如果是纯单元测试,建议mock
queryOne方法的返回值,不需要连真实数据库,测试速度和稳定性都会高很多
内容的提问来源于stack exchange,提问作者nzmattman
相关产品推荐
相关产品推荐

