DOM testing
Learn how to test DOM elements and components using Bun with happy-dom and React Testing Library
Bun's test runner plays well with existing component and DOM testing libraries, including React Testing Library and happy-dom.
happy-dom
For writing headless tests for your frontend code and components, we recommend happy-dom. Happy DOM implements a complete set of HTML and DOM APIs in plain JavaScript, making it possible to simulate a browser environment with high fidelity.
To get started install the @happy-dom/global-registrator package as a dev dependency.
$ bun add -d @happy-dom/global-registratorWe'll be using Bun's preload functionality to register the happy-dom globals before running our tests. This step will make browser APIs like document available in the global scope. Create a file called happydom.ts in the root of your project and add the following code:
import { GlobalRegistrator } from "@happy-dom/global-registrator";
GlobalRegistrator.register();To preload this file before bun test, open or create a bunfig.toml file and add the following lines.
[test]
preload = ["./happydom.ts"]This will execute happydom.ts when you run bun test. Now you can write tests that use browser APIs like document and window.
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 Support
Depending on your tsconfig.json setup, you may see a "Cannot find name 'document'" type error in the code above. To "inject" the types for document and other browser APIs, add the following triple-slash directive to the top of any test file.
/// <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");
});Let's run this test with bun test:
$ bun test
bun test v1.3.5
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 works seamlessly with React Testing Library for testing React components. After setting up happy-dom as shown above, you can install and use React Testing Library normally.
$ bun add -d @testing-library/react @testing-library/jest-dom/// <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');
});Advanced DOM Testing
Custom Elements
You can test custom elements and web components using the same setup:
/// <reference lib="dom" />
import { test, expect } from "bun:test";
test("custom element", () => {
// Define a custom element
class MyElement extends HTMLElement {
constructor() {
super();
this.innerHTML = "<p>Custom element content</p>";
}
}
customElements.define("my-element", MyElement);
// Use it in tests
document.body.innerHTML = "<my-element></my-element>";
const element = document.querySelector("my-element");
expect(element?.innerHTML).toBe("<p>Custom element content</p>");
});Event Testing
Test DOM events and user interactions:
/// <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);
});Configuration Tips
Global Setup
For more complex DOM testing setups, you can create a more comprehensive preload file:
import { GlobalRegistrator } from "@happy-dom/global-registrator";
import "@testing-library/jest-dom";
// Register happy-dom globals
GlobalRegistrator.register();
// Add any global test configuration here
global.ResizeObserver = class ResizeObserver {
observe() {}
unobserve() {}
disconnect() {}
};
// Mock other APIs as needed
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(),
})),
});Then update your bunfig.toml:
[test]
preload = ["./test-setup.ts"]Troubleshooting
Common Issues
TypeScript errors for DOM APIs: Make sure to include the /// <reference lib="dom" /> directive at the top of your test files.
Missing globals: Ensure that @happy-dom/global-registrator is properly imported and registered in your preload file.
React component rendering issues: Make sure you've installed both @testing-library/react and have happy-dom set up correctly.
Performance Considerations
Happy-dom is fast, but for very large test suites, you might want to:
- Use
beforeEachto reset the DOM state between tests - Avoid creating too many DOM elements in a single test
- Consider using
cleanupfunctions from testing libraries
import { afterEach } from "bun:test";
import { cleanup } from "@testing-library/react";
afterEach(() => {
cleanup();
document.body.innerHTML = "";
});