Skip to main content
Bun 的测试运行器与现有的组件和 DOM 测试库很好地配合,包括 React Testing Library 和 happy-dom。

happy-dom

为了为您的前端代码和组件编写无头测试,我们推荐 happy-dom。Happy DOM 在纯 JavaScript 中实现了完整的 HTML 和 DOM API 集合,使得能够高保真地模拟浏览器环境。 要开始,请将 @happy-dom/global-registrator 包作为开发依赖项安装。
terminal
bun add -d @happy-dom/global-registrator
我们将使用 Bun 的预加载功能在运行测试之前注册 happy-dom 全局变量。此步骤将使 document 等浏览器 API 在全局作用域中可用。在项目根目录下创建一个名为 happydom.ts 的文件并添加以下代码:
https://mintcdn.com/teemo/2s-4Z6VdGqiCeBNX/icons/typescript.svg?fit=max&auto=format&n=2s-4Z6VdGqiCeBNX&q=85&s=087b260066909db1cd3e9c7292bc34b2happydom.ts
import { GlobalRegistrator } from "@happy-dom/global-registrator";

GlobalRegistrator.register();
要在 bun test 之前预加载此文件,请打开或创建一个 bunfig.toml 文件并添加以下行。
bunfig.toml
[test]
preload = ["./happydom.ts"]
这将在您运行 bun test 时执行 happydom.ts。现在您可以编写使用 documentwindow 等浏览器 API 的测试。
https://mintcdn.com/teemo/2s-4Z6VdGqiCeBNX/icons/typescript.svg?fit=max&auto=format&n=2s-4Z6VdGqiCeBNX&q=85&s=087b260066909db1cd3e9c7292bc34b2dom.test.ts
import { test, expect } from "bun:test";

test("dom test", () => {
  document.body.innerHTML = `<button>My button</button>`;
  const button = document.querySelector("button");
  expect(button?.innerText).toEqual("My button");
});

TypeScript 支持

根据您的 tsconfig.json 设置,您可能会在上面的代码中看到”找不到名称’document‘“类型错误。要”注入”document 和其他浏览器 API 的类型,请在任何测试文件的顶部添加以下三斜线指令。
https://mintcdn.com/teemo/2s-4Z6VdGqiCeBNX/icons/typescript.svg?fit=max&auto=format&n=2s-4Z6VdGqiCeBNX&q=85&s=087b260066909db1cd3e9c7292bc34b2dom.test.ts
/// <reference lib="dom" />

import { test, expect } from "bun:test";

test("dom test", () => {
  document.body.innerHTML = `<button>My button</button>`;
  const button = document.querySelector("button");
  expect(button?.innerText).toEqual("My button");
});
让我们用 bun test 运行此测试:
terminal
bun test
bun test v1.3.3

dom.test.ts:
✓ dom test [0.82ms]

 1 pass
 0 fail
 1 expect() calls
Ran 1 tests across 1 files. 1 total [125.00ms]

React Testing Library

Bun 与 React Testing Library 无缝配合,用于测试 React 组件。按照上述设置好 happy-dom 后,您可以正常安装和使用 React Testing Library。
terminal
bun add -d @testing-library/react @testing-library/jest-dom
https://mintcdn.com/teemo/2s-4Z6VdGqiCeBNX/icons/typescript.svg?fit=max&auto=format&n=2s-4Z6VdGqiCeBNX&q=85&s=087b260066909db1cd3e9c7292bc34b2component.test.tsx
/// <reference lib="dom" />

import { test, expect } from 'bun:test';
import { render, screen } from '@testing-library/react';
import '@testing-library/jest-dom';

function Button({ children }: { children: React.ReactNode }) {
  return <button>{children}</button>;
}

test('renders button', () => {
  render(<Button>Click me</Button>);
  expect(screen.getByRole('button')).toHaveTextContent('Click me');
});

高级 DOM 测试

自定义元素

您可以使用相同的设置来测试自定义元素和 Web 组件:
https://mintcdn.com/teemo/2s-4Z6VdGqiCeBNX/icons/typescript.svg?fit=max&auto=format&n=2s-4Z6VdGqiCeBNX&q=85&s=087b260066909db1cd3e9c7292bc34b2custom-element.test.ts
/// <reference lib="dom" />

import { test, expect } from "bun:test";

test("custom element", () => {
  // 定义自定义元素
  class MyElement extends HTMLElement {
    constructor() {
      super();
      this.innerHTML = "<p>Custom element content</p>";
    }
  }

  customElements.define("my-element", MyElement);

  // 在测试中使用
  document.body.innerHTML = "<my-element></my-element>";
  const element = document.querySelector("my-element");
  expect(element?.innerHTML).toBe("<p>Custom element content</p>");
});

事件测试

测试 DOM 事件和用户交互:
https://mintcdn.com/teemo/2s-4Z6VdGqiCeBNX/icons/typescript.svg?fit=max&auto=format&n=2s-4Z6VdGqiCeBNX&q=85&s=087b260066909db1cd3e9c7292bc34b2events.test.ts
/// <reference lib="dom" />

import { test, expect } from "bun:test";

test("button click event", () => {
  let clicked = false;

  document.body.innerHTML = '<button id="test-btn">Click me</button>';
  const button = document.getElementById("test-btn");

  button?.addEventListener("click", () => {
    clicked = true;
  });

  button?.click();
  expect(clicked).toBe(true);
});

配置提示

全局设置

对于更复杂的 DOM 测试设置,您可以创建更全面的预加载文件:
https://mintcdn.com/teemo/2s-4Z6VdGqiCeBNX/icons/typescript.svg?fit=max&auto=format&n=2s-4Z6VdGqiCeBNX&q=85&s=087b260066909db1cd3e9c7292bc34b2test-setup.ts
import { GlobalRegistrator } from "@happy-dom/global-registrator";
import "@testing-library/jest-dom";

// 注册 happy-dom 全局变量
GlobalRegistrator.register();

// 在此处添加任何全局测试配置
global.ResizeObserver = class ResizeObserver {
  observe() {}
  unobserve() {}
  disconnect() {}
};

// 根据需要模拟其他 API
Object.defineProperty(window, "matchMedia", {
  writable: true,
  value: jest.fn().mockImplementation(query => ({
    matches: false,
    media: query,
    onchange: null,
    addListener: jest.fn(),
    removeListener: jest.fn(),
    addEventListener: jest.fn(),
    removeEventListener: jest.fn(),
    dispatchEvent: jest.fn(),
  })),
});
然后更新您的 bunfig.toml
bunfig.toml
[test]
preload = ["./test-setup.ts"]

故障排除

常见问题

DOM API 的 TypeScript 错误: 确保在测试文件顶部包含 /// <reference lib="dom" /> 指令。 缺少全局变量: 确保在预加载文件中正确导入并注册了 @happy-dom/global-registrator React 组件渲染问题: 确保您已安装 @testing-library/react 并正确设置了 happy-dom。

性能考虑

Happy-dom 很快,但对于非常大的测试套件,您可能想要:
  • 使用 beforeEach 在测试之间重置 DOM 状态
  • 避免在单个测试中创建太多 DOM 元素
  • 考虑使用测试库中的 cleanup 函数
https://mintcdn.com/teemo/2s-4Z6VdGqiCeBNX/icons/typescript.svg?fit=max&auto=format&n=2s-4Z6VdGqiCeBNX&q=85&s=087b260066909db1cd3e9c7292bc34b2test-setup.ts
import { afterEach } from "bun:test";
import { cleanup } from "@testing-library/react";

afterEach(() => {
  cleanup();
  document.body.innerHTML = "";
});