TypeScript项目中如何为Sequelize.authenticate配置两种Jest模拟?
解决Sequelize authenticate方法的多场景测试问题
你的问题核心在于jest.mock会被提升到模块顶部,导致测试内的mock逻辑无法覆盖之前的实例,且已导入的DB单例会使用真实的Sequelize构造函数,触发dialect校验错误。以下是两种可行的解决方案:
方案一:顶层Mock+动态修改mock行为
先在模块顶部统一Mock Sequelize,再在每个测试中动态调整authenticate的返回值,同时重置模块缓存确保每次测试使用新的实例。
步骤1:顶层Mock Sequelize
在测试文件最顶部添加:
import { Sequelize } from 'sequelize'; // 顶层Mock Sequelize构造函数,返回带mock authenticate方法的实例 jest.mock('sequelize', () => { const actualSequelize = jest.requireActual('sequelize'); const mockSequelizeInstance = { authenticate: jest.fn() // 先定义为空mock函数,后续修改实现 }; return { Sequelize: jest.fn(() => mockSequelizeInstance), DataTypes: actualSequelize.DataTypes, Dialect: actualSequelize.Dialect // 保留真实的Dialect类型,避免类型错误 }; });
步骤2:编写测试用例
使用jest.resetModules()重置模块缓存,确保每次测试重新导入DB实例和业务模块:
describe('健康检查测试', () => { beforeEach(() => { jest.resetModules(); // 重置模块缓存,避免单例实例复用 jest.clearAllMocks(); // 清除mock调用记录 }); test('数据库连接正常时返回健康状态', async () => { // 修改authenticate为成功返回 (Sequelize as jest.Mock).mock.results[0].value.authenticate.mockResolvedValue(undefined); // 动态导入所需模块(必须在mock修改后导入) const DBInstance = (await import('../path/to/DBInstance')).default; const HealthCheck = (await import('../path/to/HealthCheck')).default; const healthCheck = new HealthCheck(DBInstance); const result = await healthCheck.checkHealthStatus(); expect(result).toEqual(true); expect(DBInstance.authenticate).toHaveBeenCalledTimes(1); }); test('数据库连接失败时返回异常状态', async () => { // 修改authenticate为抛出错误 (Sequelize as jest.Mock).mock.results[0].value.authenticate.mockRejectedValue(new Error('mock db error')); const DBInstance = (await import('../path/to/DBInstance')).default; const HealthContext = (await import('../path/to/HealthContext')).default; const healthContext = new HealthContext(DBInstance); const result = await healthContext.checkHealthStatus(); expect(result).toEqual(false); expect(DBInstance.authenticate).toHaveBeenCalledTimes(1); }); });
方案二:每个测试独立Mock(jest.doMock)
使用jest.doMock替代jest.mock,它不会被提升到顶部,可在每个测试中单独定义Mock逻辑,配合动态导入确保实例使用当前测试的Mock。
测试用例代码
describe('健康检查测试', () => { beforeEach(() => { jest.resetModules(); // 重置模块缓存 }); test('数据库连接正常时返回健康状态', async () => { // 单独定义当前测试的Mock逻辑 jest.doMock('sequelize', () => { const actualSequelize = jest.requireActual('sequelize'); return { Sequelize: jest.fn(() => ({ authenticate: jest.fn().mockResolvedValue(undefined) })), DataTypes: actualSequelize.DataTypes }; }); // 动态导入模块 const DBInstance = (await import('../path/to/DBInstance')).default; const HealthCheck = (await import('../path/to/HealthCheck')).default; const healthCheck = new HealthCheck(DBInstance); const result = await healthCheck.checkHealthStatus(); expect(result).toEqual(true); }); test('数据库连接失败时返回异常状态', async () => { jest.doMock('sequelize', () => { const actualSequelize = jest.requireActual('sequelize'); return { Sequelize: jest.fn(() => ({ authenticate: jest.fn().mockRejectedValue(new Error('mock db error')) })), DataTypes: actualSequelize.DataTypes }; }); const DBInstance = (await import('../path/to/DBInstance')).default; const HealthContext = (await import('../path/to/HealthContext')).default; const healthContext = new HealthContext(DBInstance); const result = await healthContext.checkHealthStatus(); expect(result).toEqual(false); }); });
关键注意事项
- 避免提前导入业务模块:所有需要使用Mock的模块(如DBInstance、HealthCheck)必须在Mock定义后动态导入,否则会使用真实的Sequelize实例。
- 重置模块缓存:
jest.resetModules()会清除已导入模块的缓存,确保每次测试都重新加载模块并应用最新的Mock。 - Mock保留必要的真实类型:通过
jest.requireActual保留DataTypes、Dialect等真实类型,避免TypeScript类型错误。
内容的提问来源于stack exchange,提问作者jamesdeath123
相关产品推荐
相关产品推荐

