You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.07 06:54:02