Skip to main content
Bun’s test runner is compatible with popular DOM and component testing libraries. The recommended setup uses happy-dom to simulate a browser environment.

Setting up happy-dom

happy-dom implements a complete set of HTML and DOM APIs in JavaScript, making it possible to run browser-targeting code in Bun without a real browser.
1

Install the package

bun add -d @happy-dom/global-registrator
2

Create a preload file

Create happydom.ts at the root of your project to register the browser globals:
happydom.ts
import { GlobalRegistrator } from "@happy-dom/global-registrator";

GlobalRegistrator.register();
3

Register the preload in bunfig.toml

bunfig.toml
[test]
preload = ["./happydom.ts"]
This executes happydom.ts before any test file runs, making document, window, and other browser globals available.

Writing DOM tests

Once happy-dom is registered, you can use browser APIs directly in your tests:
dom.test.ts
/// <reference lib="dom" />

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

test("renders a button", () => {
  document.body.innerHTML = `<button id="submit">Submit</button>`;
  const button = document.querySelector("button");
  expect(button?.textContent).toBe("Submit");
});

test("handles click events", () => {
  let clicked = false;
  document.body.innerHTML = `<button id="btn">Click</button>`;
  const btn = document.getElementById("btn");

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

  expect(clicked).toBe(true);
});
Add /// <reference lib="dom" /> at the top of test files to get TypeScript type checking for browser APIs like document and window.

React Testing Library

After setting up happy-dom, install React Testing Library:
bun add -d @testing-library/react @testing-library/jest-dom
Button.test.tsx
/// <reference lib="dom" />

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

function Counter() {
  const [count, setCount] = React.useState(0);
  return (
    <div>
      <span data-testid="count">{count}</span>
      <button onClick={() => setCount(c => c + 1)}>Increment</button>
    </div>
  );
}

test("increments counter on click", () => {
  render(<Counter />);

  expect(screen.getByTestId("count")).toHaveTextContent("0");
  fireEvent.click(screen.getByRole("button", { name: "Increment" }));
  expect(screen.getByTestId("count")).toHaveTextContent("1");
});

Testing custom elements

happy-dom supports the Custom Elements API:
custom-element.test.ts
/// <reference lib="dom" />

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

test("custom element renders content", () => {
  class AlertBox extends HTMLElement {
    constructor() {
      super();
      this.innerHTML = `<div class="alert">${this.getAttribute("message")}</div>`;
    }
  }

  customElements.define("alert-box", AlertBox);

  document.body.innerHTML = `<alert-box message="Something went wrong"></alert-box>`;
  const box = document.querySelector("alert-box");
  expect(box?.querySelector(".alert")?.textContent).toBe("Something went wrong");
});

Advanced setup

For projects that need additional globals polyfilled (e.g., ResizeObserver, matchMedia), create a more comprehensive preload file:
test-setup.ts
import { GlobalRegistrator } from "@happy-dom/global-registrator";
import "@testing-library/jest-dom";
import { afterEach } from "bun:test";
import { cleanup } from "@testing-library/react";

// Register all browser globals
GlobalRegistrator.register();

// Polyfill APIs not in happy-dom
global.ResizeObserver = class ResizeObserver {
  observe() {}
  unobserve() {}
  disconnect() {}
};

// Clean up after each test
afterEach(() => {
  cleanup();
  document.body.innerHTML = "";
});
bunfig.toml
[test]
preload = ["./test-setup.ts"]

JSDOM as an alternative

If you prefer jsdom, install it and register it similarly:
bun add -d jsdom
jsdom-setup.ts
import { JSDOM } from "jsdom";

const dom = new JSDOM("<!DOCTYPE html><html><body></body></html>");

global.window = dom.window as unknown as Window & typeof globalThis;
global.document = dom.window.document;
global.navigator = dom.window.navigator;
bunfig.toml
[test]
preload = ["./jsdom-setup.ts"]

Troubleshooting

Add the triple-slash DOM reference to the top of the file:
/// <reference lib="dom" />
Alternatively, add "lib": ["dom"] to your tsconfig.json.
Ensure your preload file is registered in bunfig.toml and that GlobalRegistrator.register() is called before any tests run.
Confirm that both @testing-library/react and @happy-dom/global-registrator are installed as dev dependencies, and that the preload file is loaded before test files.
Reset the DOM in afterEach to prevent state from one test affecting the next:
import { afterEach } from "bun:test";
import { cleanup } from "@testing-library/react";

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

Build docs developers (and LLMs) love