使用Jest与React Testing Library测试图片尺寸校验组件时image.onload无法完成的问题求助
解决Jest/React Testing Library中Image.onload不触发及尺寸获取问题
在Jest的Node.js测试环境中,原生的Image对象并没有真实的浏览器实现,所以默认情况下image.onload不会触发,直接模拟也会导致width/height为undefined。我们可以通过正确模拟Image类+修复组件代码中的小问题来解决这个问题。
第一步:修复组件中的代码漏洞
你的checkDimension函数里,image.onerror的赋值放在了onload回调内部,这会导致如果图片加载失败,onerror永远不会被绑定,Promise会一直处于pending状态。先把这个问题修复:
const checkDimension = async (result) => { const image = new Image(); const promiseImg = new Promise( (resolve, reject) => { console.log('Started'); // 正确绑定onload image.onload = () => { resolve(image.height <= 100 && image.width <= 100); }; // 将onerror移到外部,确保加载失败时能触发reject image.onerror = (error) => reject(error); }); image.src = result; const isValid = await promiseImg; console.log('finished with result', isValid); return isValid; };
第二步:在Jest中模拟带有尺寸的Image对象
我们需要自定义一个MockImage类,它能根据测试用的图片数据设置对应的宽高,并自动触发onload。可以通过识别测试中使用的base64字符串来区分不同的图片尺寸:
import React from 'react'; import { render, screen, fireEvent } from '@testing-library/react'; import Avatar from './avatar'; describe('Avatar test dimensions', () => { let uploader; let originalImage; beforeAll(() => { // 保存原生Image,测试结束后恢复 originalImage = global.Image; global.Image = class MockImage { constructor() { this.onload = null; this.onerror = null; this.width = 0; this.height = 0; } // 通过setter拦截src赋值,根据base64内容设置对应尺寸 set src(dataUrl) { // 匹配第一个测试用的102x1图片的base64前缀 if (dataUrl.includes('iVBORw0KGgoAAAANSUhEUgAAAGYAAAABCAYAAAAsLtu')) { this.width = 102; this.height = 1; } // 匹配第二个测试用的2x2图片的base64前缀 else if (dataUrl.includes('iVBORw0KGgoAAAANSUhEUgAAAAIAAAACCAYAAABytg0k')) { this.width = 2; this.height = 2; } // 异步触发onload,模拟浏览器加载图片的异步行为 setTimeout(() => { if (this.onload) this.onload(); }, 0); } }; }); afterAll(() => { // 恢复原生Image,避免影响其他测试 global.Image = originalImage; }); beforeEach(() => { render(<Avatar />); uploader = screen.getByTestId('imageInput'); }); it('should give error for wrong dimension of real image file with size 102x1px', async ()=>{ const file = new File( ['iVBORw0KGgoAAAANSUhEUgAAAGYAAAABCAYAAAAsLtuAAAAAEklEQVR42mNkYPhfzzAKBh0AAGClAYCUny1uAAAAAElFTkSuQmCC'], 'image102x1.png', { type: 'image/png' } ); fireEvent.change(uploader, {target: { files: [file] } }); expect(await screen.findByText('Wrong dimension')).toBeInTheDocument(); }); it('should pass real image less than equal 100x100',async ()=>{ const file = new File(['iVBORw0KGgoAAAANSUhEUgAAAAIAAAACCAYAAABytg0kAAAAEklEQVR42mP8/5/hPwMQMMIYAELqBf31meVMAAAAAElFTkSuQmCC'], 'image2x2.png', { type: 'image/png' }); fireEvent.change(uploader, {target: { files: [file] } }); expect(await screen.findByText('Good dimension')).toBeInTheDocument(); }); });
其他可选方案
如果你的测试用例更多,或者不想通过base64字符串判断,可以:
- 给测试用的
File对象添加自定义属性(比如customWidth/customHeight),然后在组件的handleImageLoad中把这些属性传递给checkDimension,再在模拟Image时使用这些值。 - 使用
jest.spyOn动态设置Image的宽高,针对每个测试用例单独配置。
内容的提问来源于stack exchange,提问作者user2408962
相关产品推荐
相关产品推荐

