---
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 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.

```bash terminal icon="terminal"
bun add -d @happy-dom/global-registrator
```

We'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:

```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"]
```

This will execute `happydom.ts` when you run `bun test`. Now you can write tests that 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 = `<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.

```ts title="dom.test.ts" icon="/icons/typescript.svg"
/// <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`:

```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 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.

```bash terminal icon="terminal"
bun add -d @testing-library/react @testing-library/jest-dom
```

```ts title="component.test.tsx" icon="/icons/typescript.svg"
/// <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:

```ts title="custom-element.test.ts" icon="/icons/typescript.svg"
/// <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:

```ts title="events.test.ts" icon="/icons/typescript.svg"
/// <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:

```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**: 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 `beforeEach` to reset the DOM state between tests
- Avoid creating too many DOM elements in a single test
- Consider using `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 = "";
});
```
