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

React组件mock document.body.append时出现appendChild未定义错误求助

如何正确Mock document.body.append避免React测试报错?

问题场景

有一个React组件Test,在useEffect中通过document.body.append添加内联script标签:

// Test.tsx
import { useEffect } from 'react';

const Test = () => {
  useEffect(() => {
    const inlineScript = document.createElement('script');
    inlineScript.setAttribute('data-name', 'john');
    inlineScript.innerHTML = 'console.log(123);';
    document.body.append(inlineScript);
  }, []);

  return <div>Test</div>;
};

export default Test;

编写测试用例时,尝试用Object.defineProperty重写document.body来mockappend方法,执行测试时出现TypeError: baseElement.appendChild is not a function报错:

// Test.spec.tsx(错误写法)
import { render } from '@testing-library/react';
import '@testing-library/jest-dom';
import Test from './Test';

Object.defineProperty(document, 'body', {
  value: {
    append: jest.fn()
  },
  writable: true,
  configurable: true
});

describe('<Test /> component test', () => {
  it('should work', () => {
    render(<Test />);
  });
});

依赖版本:

"react": "^17.0.2",
"react-dom": "^17.0.2",
"@testing-library/jest-dom": "^5.14.1",
"@testing-library/react": "^12.1.2",
"@testing-library/react-hooks": "^8.0.0",
"@testing-library/user-event": "^14.4.3",

错误原因

直接替换整个document.body对象时,只保留了自定义的append方法,丢失了body原本的原生DOM方法(比如appendChild)。而@testing-library/react的render方法需要调用这些原生方法挂载组件,因此触发报错。

解决方案

不要完全替换document.body,而是在原有对象基础上mockappend方法,保留其他原生属性和方法。推荐用jest.spyOn实现,既安全又便于测试后恢复。

修改后的测试代码

// Test.spec.tsx(正确写法)
import { render } from '@testing-library/react';
import '@testing-library/jest-dom';
import Test from './Test';

describe('<Test /> component test', () => {
  let appendMock: jest.SpyInstance;

  // 每个测试前mock append方法
  beforeEach(() => {
    appendMock = jest.spyOn(document.body, 'append').mockImplementation(() => {});
  });

  // 每个测试后恢复原方法,避免影响其他用例
  afterEach(() => {
    appendMock.mockRestore();
  });

  it('should call document.body.append with the correct script element', () => {
    render(<Test />);

    // 验证append调用次数
    expect(appendMock).toHaveBeenCalledTimes(1);
    
    // 验证传入的script参数符合预期
    const [scriptElement] = appendMock.mock.calls[0];
    expect(scriptElement).toBeInstanceOf(HTMLScriptElement);
    expect(scriptElement.getAttribute('data-name')).toBe('john');
    expect(scriptElement.innerHTML).toBe('console.log(123);');
  });
});

替代方案(不推荐)

如果必须使用Object.defineProperty,需要继承原有body的所有属性和方法:

// Test.spec.tsx(替代写法)
import { render } from '@testing-library/react';
import '@testing-library/jest-dom';
import Test from './Test';

describe('<Test /> component test', () => {
  let originalBody: HTMLElement;

  beforeEach(() => {
    originalBody = document.body;
    Object.defineProperty(document, 'body', {
      value: {
        ...originalBody,
        append: jest.fn()
      },
      writable: true,
      configurable: true
    });
  });

  afterEach(() => {
    Object.defineProperty(document, 'body', {
      value: originalBody,
      writable: true,
      configurable: true
    });
  });

  it('should work', () => {
    render(<Test />);
    expect(document.body.append).toHaveBeenCalled();
  });
});

这种方法需要手动保存和恢复原body对象,不如jest.spyOn简洁,优先推荐第一种方案。

内容的提问来源于stack exchange,提问作者LY Hooi

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.02 09:20:22