執行環境套件管理器打包器測試執行器程式碼檢查格式化指南參考部落格意見回饋

DOM 測試

學習如何使用 Bun 搭配 happy-dom 和 React Testing Library 測試 DOM 元素和元件

Bun 的測試執行器可以很好地配合現有的元件和 DOM 測試庫使用,包括 React Testing Library 和 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 的檔案,並新增以下程式碼:

happydom.ts
import { GlobalRegistrator } from "@happy-dom/global-registrator";

GlobalRegistrator.register();

要在執行 bun test 之前預載入此檔案,請開啟或建立 bunfig.toml 檔案,並新增以下內容:

bunfig.toml
[test]
preload = ["./happydom.ts"]

bun test 現在會在測試之前執行 happydom.ts,因此測試可以使用 document 和 window 等瀏覽器 API。

dom.test.ts
import { test, expect } from "bun:test";

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

TypeScript 支援

根據你的 tsconfig.json 設定,你可能會在上面的程式碼中看到 "Cannot find name 'document'" 型別錯誤。要載入 document 和其他瀏覽器 API 的型別,請將以下三斜線指令新增到任意測試檔案的頂部。

dom.test.ts
/// <reference lib="dom" />

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

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

使用 bun test 執行測試:

terminal
bun test
Code
bun test v1.3.3

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

 1 pass
 0 fail
 1 expect() calls
Ran 1 test across 1 file. [125.00ms]

React 測試庫

Bun 支援使用 React Testing Library 測試 React 元件。按照前文所述設定 happy-dom 後,正常安裝並使用 React Testing Library。

terminal
bun add -d @testing-library/react @testing-library/jest-dom
component.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('渲染按鈕', () => {
  render(<Button>點選我</Button>);
  expect(screen.getByRole('button')).toHaveTextContent('點選我');
});

高階 DOM 測試

自定義元素

使用相同的設定測試自定義元素和 Web 元件:

custom-element.test.ts
/// <reference lib="dom" />

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

test("自定義元素", () => {
  // 定義一個自定義元素
  class MyElement extends HTMLElement {
    constructor() {
      super();
      this.innerHTML = "<p>自定義元素內容</p>";
    }
  }

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

  // 在測試中使用它
  document.body.innerHTML = "<my-element></my-element>";
  const element = document.querySelector("my-element");
  expect(element?.innerHTML).toBe("<p>自定義元素內容</p>");
});

事件測試

測試 DOM 事件和使用者互動:

events.test.ts
/// <reference lib="dom" />

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

test("按鈕點選事件", () => {
  let clicked = false;

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

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

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

設定建議

全域性設定

對於更復雜的設定,請建立一個同時註冊全域性模擬的預載入檔案:

test-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" /> 指令。

缺少全域性變數:檢查 preload 檔案是否匯入並註冊了 @happy-dom/global-registrator。

React 元件渲染問題:檢查是否已安裝 @testing-library/react,並確認 happy-dom 已完成設定。

效能考慮

happy-dom 速度很快,但對於非常大型的測試套件,你可能需要:

  • 使用 beforeEach 在測試之間重置 DOM 狀態
  • 避免在單個測試中建立過多 DOM 元素
  • 使用測試庫提供的 cleanup 函式
test-setup.ts
import { afterEach } from "bun:test";
import { cleanup } from "@testing-library/react";

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