使用Mocha/Chai/Sinon测试异步image.onload处理器
我之前也碰到过类似的异步回调测试问题,尤其是image.onload这种依赖浏览器事件的场景,结合你用到的Sinon、Mocha和Chai,给你几个实用的解决思路:
解决Image.onload异步测试的核心方案
1. 手动模拟Image对象并主动触发onload
浏览器里的Image加载是异步的,测试环境中我们可以直接模拟一个Image实例,跳过真实的网络请求,手动触发它的onload回调来验证逻辑。
示例代码:
describe('RenderObject', () => { let sandbox; let mockImage; beforeEach(() => { sandbox = Sinon.createSandbox(); // 构造一个模拟的Image对象,预设图片尺寸 mockImage = { src: '', onload: null, width: 100, height: 200 }; // 替换全局的Image构造函数,让RenderObject创建我们的模拟实例 sandbox.stub(global, 'Image').returns(mockImage); }); afterEach(() => { // 测试后恢复所有stub,避免污染其他测试 sandbox.restore(); }); it('should set correct dimensions for objects after image loads', (done) => { // 创建RenderObject实例 const renderObj = new RenderObject('test-image.png'); // 验证初始状态下尺寸未被设置 expect(renderObj.width).to.be.undefined; expect(renderObj.height).to.be.undefined; // 手动触发onload回调,模拟图片加载完成 mockImage.onload(); // 验证尺寸是否被正确赋值 expect(renderObj.width).to.equal(100); expect(renderObj.height).to.equal(200); done(); // 告诉Mocha异步测试已完成 }); });
2. 用Sinon Fake Timers同步处理异步流程
如果你的代码依赖浏览器事件循环,Sinon的Fake Timers可以帮你把异步回调同步执行,不用依赖done()函数。
示例代码:
it('should update dimensions synchronously using fake timers', () => { const clock = Sinon.useFakeTimers(); const mockImage = { src: '', onload: null, width: 150, height: 150 }; Sinon.stub(global, 'Image').returns(mockImage); const renderObj = new RenderObject('test.png'); // 触发onload并强制执行所有待处理的回调 mockImage.onload(); clock.tick(0); // 验证尺寸设置正确 expect(renderObj.width).to.equal(150); expect(renderObj.height).to.equal(150); // 清理资源 clock.restore(); Sinon.restore(); });
3. 验证处理器函数的绑定与调用
有时候问题可能出在构造函数的绑定逻辑上,你可以用Sinon Spy来确认处理器函数是否在onload时被正确调用。
示例代码:
it('should bind the dimension handler to image.onload', () => { // 假设你的尺寸设置逻辑是RenderObject的原型方法setDimensions const setDimensionsSpy = Sinon.spy(RenderObject.prototype, 'setDimensions'); const mockImage = { src: '', onload: null }; Sinon.stub(global, 'Image').returns(mockImage); // 创建实例,触发onload new RenderObject('test.jpg'); mockImage.onload(); // 验证处理器函数被调用了一次 assert(setDimensionsSpy.calledOnce); // 清理spy和stub setDimensionsSpy.restore(); Sinon.restore(); });
额外注意事项
- 如果是在Node.js环境下测试,需要用
jsdom模拟浏览器环境,这样全局的Image对象才会存在,才能被Sinon stub。 - 若你的逻辑是更新所有使用同一图片源的对象,测试时要创建多个RenderObject实例,验证它们的尺寸都被正确更新。
- 务必在每个测试后恢复Sinon的stubs/spies,避免测试用例之间的状态污染。
内容的提问来源于stack exchange,提问作者Jonathan




