--- title: "DOM testing" description: "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 headless tests of your frontend code and components, we recommend happy-dom. It implements a complete set of HTML and DOM APIs in plain JavaScript, so it can simulate a browser environment with high fidelity. Install the `@happy-dom/global-registrator` package as a dev dependency. ```bash terminal icon="terminal" bun add -d @happy-dom/global-registrator ``` Use Bun's preload feature to register the happy-dom globals before your tests run, which makes browser APIs like `document` available in the global scope. Create a file called `happydom.ts` in the root of your project with the following code: ```ts title="happydom.ts" icon="/icons/typescript.svg" 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. ```toml title="bunfig.toml" icon="settings" [test] preload = ["./happydom.ts"] ``` `bun test` now executes `happydom.ts` before your tests, so they can use browser APIs like `document` and `window`. ```ts title="dom.test.ts" icon="/icons/typescript.svg" import { test, expect } from "bun:test"; test("dom test", () => { document.body.innerHTML = ``; 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 earlier code. To load the types for `document` and other browser APIs, add the following triple-slash directive to the top of any test file. ```ts title="dom.test.ts" icon="/icons/typescript.svg" /// import { test, expect } from "bun:test"; test("dom test", () => { document.body.innerHTML = ``; const button = document.querySelector("button"); expect(button?.innerText).toEqual("My button"); }); ``` Run the test with `bun test`: ```bash terminal icon="terminal" bun test ``` ``` bun test v1.3.3 dom.test.ts: ✓ dom test [0.82ms] 1 pass 0 fail 1 expect() calls Ran 1 test across 1 file. [125.00ms] ``` ## React Testing Library Bun works with React Testing Library for testing React components. After setting up happy-dom as described earlier, install and use React Testing Library normally. ```bash terminal icon="terminal" bun add -d @testing-library/react @testing-library/jest-dom ``` ```ts title="component.test.tsx" icon="/icons/typescript.svg" /// 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 ; } test('renders button', () => { render(); expect(screen.getByRole('button')).toHaveTextContent('Click me'); }); ``` ## Advanced DOM Testing ### Custom Elements Test custom elements and web components with the same setup: ```ts title="custom-element.test.ts" icon="/icons/typescript.svg" /// import { test, expect } from "bun:test"; test("custom element", () => { // Define a custom element class MyElement extends HTMLElement { constructor() { super(); this.innerHTML = "

Custom element content

"; } } customElements.define("my-element", MyElement); // Use it in tests document.body.innerHTML = ""; const element = document.querySelector("my-element"); expect(element?.innerHTML).toBe("

Custom element content

"); }); ``` ### Event Testing Test DOM events and user interactions: ```ts title="events.test.ts" icon="/icons/typescript.svg" /// import { test, expect } from "bun:test"; test("button click event", () => { let clicked = false; document.body.innerHTML = ''; const button = document.getElementById("test-btn"); button?.addEventListener("click", () => { clicked = true; }); button?.click(); expect(clicked).toBe(true); }); ``` ## Configuration Tips ### Global Setup For more involved setups, create a preload file that also registers global mocks: ```ts title="test-setup.ts" icon="/icons/typescript.svg" 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`: ```toml title="bunfig.toml" icon="settings" [test] preload = ["./test-setup.ts"] ``` ## Troubleshooting ### Common Issues **TypeScript errors for DOM APIs**: Include the `/// ` directive at the top of your test files. **Missing globals**: Check that your preload file imports and registers `@happy-dom/global-registrator`. **React component rendering issues**: Check that `@testing-library/react` is installed and happy-dom is set up. ### Performance Considerations happy-dom is fast, but for very large test suites you may want to: - Use `beforeEach` to reset the DOM state between tests - Avoid creating too many DOM elements in a single test - Use `cleanup` functions from testing libraries ```ts title="test-setup.ts" icon="/icons/typescript.svg" import { afterEach } from "bun:test"; import { cleanup } from "@testing-library/react"; afterEach(() => { cleanup(); document.body.innerHTML = ""; }); ```