如何为Jest测试Stub异步模块依赖?超时问题排查求助
异步模块依赖的Jest测试Stub最佳实现方案
问题根源
你遇到的超时问题,核心是Stub返回的Promise处于永久pending状态——手动创建Promise时没有调用resolve或reject,导致await example(...)一直阻塞,最终触发Jest的超时限制。
两种最佳实现方案
方案一:用Sinon正确处理异步Stub
Sinon提供了专门的resolves和rejects方法,替代手动创建Promise,确保异步状态正确流转:
import * as exampleModule from '../some/place' import { processRequest } from '../routes/processRequest' import sinon from 'sinon' describe('processRequest test', () => { let exampleStub: sinon.SinonStub // 每个测试前创建Stub,测试后恢复原始方法 beforeEach(() => { exampleStub = sinon.stub(exampleModule, 'example').resolves('mock-processed-data') }) afterEach(() => { exampleStub.restore() }) it('should respond with 200 when example succeeds', async () => { // 模拟请求、响应、next函数 const mockRequest = { body: { someData: 'input-test-data' } } as Request const mockResponse = { status: sinon.stub().returnsThis(), send: sinon.stub() } as unknown as Response const mockNext = sinon.stub() await processRequest(mockRequest, mockResponse, mockNext) // 验证核心逻辑 expect(exampleStub.calledWith('input-test-data')).toBe(true) expect(mockResponse.status.calledWith(200)).toBe(true) expect(mockResponse.send.called()).toBe(true) expect(mockNext.called()).toBe(false) }) it('should call next with error when example fails', async () => { const testError = new Error('example-execution-failed') exampleStub.rejects(testError) const mockRequest = { body: { someData: 'invalid-data' } } as Request const mockResponse = { status: sinon.stub(), send: sinon.stub() } as unknown as Response const mockNext = sinon.stub() await processRequest(mockRequest, mockResponse, mockNext) expect(mockNext.calledWith(testError)).toBe(true) expect(mockResponse.status.called()).toBe(false) }) })
核心要点:
- 用
resolves/rejects替代手动创建Promise,避免pending状态 - 通过
beforeEach/afterEach管理Stub的生命周期,防止测试用例间污染 - 增加断言验证Stub调用、响应方法执行等逻辑,确保测试完整性
方案二:直接使用Jest内置Mock功能(无需Sinon)
Jest本身内置了完善的Mock能力,写法更统一,无需额外依赖:
// 提前mock目标模块 jest.mock('../some/place', () => ({ example: jest.fn() })) import { example } from '../some/place' import { processRequest } from '../routes/processRequest' describe('processRequest test', () => { it('should return 200 on successful example call', async () => { // 设置异步返回值 (example as jest.Mock).mockResolvedValue('mock-result') const mockRequest = { body: { someData: 'test-input' } } as Request const mockResponse = { status: jest.fn().mockReturnThis(), send: jest.fn() } as unknown as Response const mockNext = jest.fn() await processRequest(mockRequest, mockResponse, mockNext) expect(example).toHaveBeenCalledWith('test-input') expect(mockResponse.status).toHaveBeenCalledWith(200) expect(mockResponse.send).toHaveBeenCalled() expect(mockNext).not.toHaveBeenCalled() }) it('should propagate error via next when example fails', async () => { const testError = new Error('mock-error') (example as jest.Mock).mockRejectedValue(testError) const mockRequest = { body: { someData: 'bad-input' } } as Request const mockResponse = { status: jest.fn(), send: jest.fn() } as unknown as Response const mockNext = jest.fn() await processRequest(mockRequest, mockResponse, mockNext) expect(mockNext).toHaveBeenCalledWith(testError) expect(mockResponse.status).not.toHaveBeenCalled() }) })
核心要点:
- 用
jest.mock直接mock整个模块,返回Jest mock函数 - 用
mockResolvedValue/mockRejectedValue快速设置异步返回值 - 利用Jest内置的断言方法(
toHaveBeenCalledWith等)简化验证逻辑
关键注意事项
- 禁止手动创建不调用
resolve/reject的Promise作为Stub返回值,这是测试超时的常见诱因 - 测试后必须恢复原始模块或清除Mock,避免不同测试用例间的状态污染
- 同时覆盖成功、异常分支,确保代码逻辑的完整性验证
内容的提问来源于stack exchange,提问作者Abraham P
相关产品推荐
相关产品推荐

