如何用Jest同时模拟包含构造函数和函数的模块?
问题分析与解决方案
你的核心问题有两个:
- 模块顶层的
smCli实例创建时机,需确保Jest的mock完全生效后再初始化(不过jest.mock会被自动提升到模块最顶部,所以主要问题是mock的login方法未返回Promise,以及未正确捕获实例的mock方法) - 你给
login方法返回的是普通值2,但原代码中用await调用它,所以login必须返回Promise,否则会报错或导致测试逻辑不生效
修正后的测试代码
import * as sm from 'some-client'; import { getKey } from './your-module-file'; // 替换成你的实际模块路径 // 自动模拟整个some-client模块 jest.mock('some-client'); // 类型断言,让TypeScript识别mock类型 const MockClient = sm.Client as jest.Mock; const mockGetCreds = sm.getCreds as jest.Mock<Promise<unknown>>; describe('getKey', () => { let mockLogin: jest.Mock; beforeEach(() => { // 重置所有mock的调用记录和返回值 MockClient.mockClear(); mockGetCreds.mockClear(); // 定义login方法的mock,返回resolved Promise mockLogin = jest.fn().mockResolvedValue(undefined); // 让Client类的实例返回包含mockLogin的对象 MockClient.mockImplementation(() => ({ login: mockLogin })); // 模拟getCreds的返回值 mockGetCreds.mockResolvedValue({ username: 'test', password: 'test' }); }); it('should call getCreds and login with correct credentials', async () => { await getKey(); // 断言getCreds被调用了一次 expect(mockGetCreds).toHaveBeenCalledTimes(1); // 断言Client被实例化了一次 expect(MockClient).toHaveBeenCalledTimes(1); // 断言login被调用,参数是getCreds返回的凭据 expect(mockLogin).toHaveBeenCalledWith({ username: 'test', password: 'test' }); }); });
关键修正点
- 给
login方法的mock返回mockResolvedValue,确保它是一个Promise,符合原代码中await的调用逻辑 - 在
beforeEach里重置mock,避免不同测试用例之间的污染 - 单独提取
mockLogin变量,方便后续断言它的调用情况
另外,如果你不想用beforeEach,也可以直接在模块级别设置mock,但重置mock是好习惯,能保证测试的独立性。
内容的提问来源于stack exchange,提问作者Jozef
相关产品推荐
相关产品推荐

