Skip to main content
Bun includes built-in support for Web APIs, making it easy to test DOM-related code without additional setup.

Built-in DOM APIs

Bun provides these Web APIs out of the box:
  • fetch
  • Request, Response, Headers
  • URL, URLSearchParams
  • Blob, File
  • FormData
  • TextEncoder, TextDecoder
  • ReadableStream, WritableStream, TransformStream
  • WebSocket
  • EventTarget, Event, CustomEvent
  • AbortController, AbortSignal
  • structuredClone
import { test, expect } from "bun:test";

test("fetch API", async () => {
  const response = await fetch("https://api.example.com/data");
  expect(response.ok).toBe(true);
  const data = await response.json();
  expect(data).toBeDefined();
});

test("URL parsing", () => {
  const url = new URL("https://example.com/path?foo=bar");
  expect(url.hostname).toBe("example.com");
  expect(url.searchParams.get("foo")).toBe("bar");
});

Testing with happy-dom

For full DOM support, use happy-dom:
$ bun add -d happy-dom
Create a test setup file:
#test/setup.ts
import { GlobalRegistrator } from "@happy-dom/global-registrator";

GlobalRegistrator.register();
Configure in bunfig.toml:
[test]
preload = ["./test/setup.ts"]
Now you can use DOM APIs in tests:
import { test, expect } from "bun:test";

test("DOM manipulation", () => {
  document.body.innerHTML = `
    <div id="app">
      <h1>Hello World</h1>
    </div>
  `;
  
  const app = document.getElementById("app");
  expect(app).toBeDefined();
  expect(app?.querySelector("h1")?.textContent).toBe("Hello World");
});

Testing React components

Test React components with @testing-library/react:
$ bun add -d @testing-library/react happy-dom
Setup file:
#test/setup.ts
import { GlobalRegistrator } from "@happy-dom/global-registrator";

GlobalRegistrator.register();
Test a component:
import { test, expect } from "bun:test";
import { render, screen } from "@testing-library/react";

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

test("Button renders", () => {
  const handleClick = jest.fn();
  render(<Button onClick={handleClick}>Click me</Button>);
  
  const button = screen.getByText("Click me");
  expect(button).toBeDefined();
  
  button.click();
  expect(handleClick).toHaveBeenCalledTimes(1);
});

Testing with jsdom

Alternatively, use jsdom:
$ bun add -d jsdom
Setup file:
#test/setup.ts
import { JSDOM } from "jsdom";

const dom = new JSDOM("<!DOCTYPE html>");
global.document = dom.window.document;
global.window = dom.window as any;

Testing Web Components

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

class MyElement extends HTMLElement {
  connectedCallback() {
    this.innerHTML = "<p>Hello from Web Component</p>";
  }
}

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

test("Web Component", () => {
  const element = document.createElement("my-element");
  document.body.appendChild(element);
  
  expect(element.innerHTML).toBe("<p>Hello from Web Component</p>");
});

Testing event handling

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

test("event handling", () => {
  const button = document.createElement("button");
  const handleClick = jest.fn();
  
  button.addEventListener("click", handleClick);
  button.click();
  
  expect(handleClick).toHaveBeenCalledTimes(1);
});

Testing async DOM updates

import { test, expect } from "bun:test";
import { waitFor } from "@testing-library/react";

test("async DOM update", async () => {
  const div = document.createElement("div");
  
  setTimeout(() => {
    div.textContent = "Updated";
  }, 100);
  
  await waitFor(() => {
    expect(div.textContent).toBe("Updated");
  });
});

Cleanup

Clean up DOM between tests:
import { afterEach } from "bun:test";

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

Accessing window properties

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

test("window properties", () => {
  expect(window.location.href).toBe("http://localhost/");
  expect(window.innerWidth).toBeGreaterThan(0);
});

Form testing

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

test("form submission", () => {
  document.body.innerHTML = `
    <form id="myForm">
      <input name="username" value="alice" />
      <input name="email" value="[email protected]" />
    </form>
  `;
  
  const form = document.getElementById("myForm") as HTMLFormElement;
  const formData = new FormData(form);
  
  expect(formData.get("username")).toBe("alice");
  expect(formData.get("email")).toBe("[email protected]");
});

Build docs developers (and LLMs) love