如何在Jest中mock SVGTextElement的getBBox方法测试文本测量函数
问题根因
你使用的测试运行环境(通常是jsdom)没有完整实现SVG元素的API,getBBox属于依赖浏览器实际渲染能力的接口,默认没有提供,所以会报错。你之前仅给SVGTextElement原型加方法的方式未生效,是因为jsdom创建的svg元素实例不一定绑定你修改后的全局构造函数原型,而且类型校验也会因为缺少其他属性报错。
可行方案
方案1:全局mock createElementNS(无需修改业务代码)
直接拦截元素创建逻辑,给svg text元素加上需要的mock方法即可,测试代码示例:
import { measureText } from '../measure'; describe('measureText', () => { beforeEach(() => { // 备份原始创建元素方法 const originalCreateElementNS = document.createElementNS; jest.spyOn(document, 'createElementNS').mockImplementation((namespace, tag) => { if (tag === 'text') { // mock text元素的所有用到的方法 return { setAttribute: jest.fn(), textContent: '', getBBox: jest.fn(() => ({ width: 100, height: 20 })) } as any; } if (tag === 'svg') { // mock svg元素的appendChild方法 return { appendChild: jest.fn() } as any; } // 其他元素走原生逻辑 return originalCreateElementNS(namespace, tag); }); // mock body的appendChild避免插入节点报错 jest.spyOn(document.body, 'appendChild').mockImplementation(jest.fn()); }); afterEach(() => { // 恢复所有mock,避免影响其他用例 jest.restoreAllMocks(); }); it('should return correct text width and height', () => { const textSize = measureText({ label: '12345', fontFamily: 'Roboto', size: 20 }); // 验证返回值和mock的一致 expect(textSize).toEqual({ width: 100, height: 20 }); }); });
方案2:依赖注入(代码可测性更强)
把函数内部依赖的svg、text元素改成可注入的可选参数,生产环境不用额外传参,测试时直接传入mock对象,不需要修改全局环境。
改造后的业务代码示例:
const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg'); const text = document.createElementNS('http://www.w3.org/2000/svg', 'text'); svg.appendChild(text); interface HeightAndWidth { width: number; height: number; } interface MeasureProps { label: string; fontFamily: string; size: number; fontWeight?: number; lineHeight?: number; letterSpacing?: string; } // 新增可选的元素参数,测试时传入mock即可 export const measureText = ({ label, fontFamily, size, fontWeight = 400, lineHeight = 1, letterSpacing = '0', }: MeasureProps, textElement = text, svgElement = svg ): HeightAndWidth => { textElement.setAttribute('font-size', `${size}px`); textElement.setAttribute('line-height', `${lineHeight}px`); textElement.setAttribute('font-family', fontFamily); textElement.setAttribute('font-weight', `${fontWeight}`); textElement.setAttribute('letter-spacing', letterSpacing); textElement.textContent = label; document.body.appendChild(svgElement); const { width, height } = textElement.getBBox(); // 补充移除节点逻辑,避免原代码的DOM泄漏问题 document.body.removeChild(svgElement); return { width, height, }; };
对应的测试代码无需修改全局环境,直接传入mock对象即可:
import { measureText } from '../measure'; describe('measureText', () => { it('should return correct text width and height', () => { // 直接构造mock的text元素 const mockText = { setAttribute: jest.fn(), textContent: '', getBBox: jest.fn(() => ({ width: 100, height: 20 })) } as any; const mockSvg = { appendChild: jest.fn() } as any; const textSize = measureText({ label: '12345', fontFamily: 'Roboto', size: 20 }, mockText, mockSvg); expect(textSize).toEqual({ width: 100, height: 20 }); }); });
内容的提问来源于stack exchange,提问作者Kreha
相关产品推荐
相关产品推荐

