> ## Documentation Index
> Fetch the complete documentation index at: https://mintlify.com/preactjs/preact/llms.txt
> Use this file to discover all available pages before exploring further.

# Testing

> Testing utilities and patterns for Preact applications with preact/test-utils

# Testing

Preact provides testing utilities in the `preact/test-utils` package to make testing components easier. These utilities help you control the rendering lifecycle, flush effects synchronously, and write reliable tests.

## Installation

The test utilities are included in the preact package:

```bash theme={null}
npm install --save-dev preact
```

You'll also want a test runner and assertion library:

```bash theme={null}
# Using Vitest (recommended)
npm install --save-dev vitest @testing-library/preact

# Or Jest
npm install --save-dev jest @testing-library/preact
```

## Importing Test Utils

```javascript theme={null}
import { act, setupRerender, teardown } from 'preact/test-utils';
import { render } from '@testing-library/preact';
```

Source: `test-utils/src/index.js`

## setupRerender()

Sets up a function to flush pending renders synchronously. By default, Preact batches renders asynchronously. In tests, you want renders to happen immediately.

```javascript theme={null}
import { setupRerender } from 'preact/test-utils';
import { render } from 'preact';
import { useState } from 'preact/hooks';

function Counter() {
  const [count, setCount] = useState(0);
  return (
    <div>
      <span>{count}</span>
      <button onClick={() => setCount(count + 1)}>Increment</button>
    </div>
  );
}

test('counter increments', () => {
  const rerender = setupRerender();
  
  const container = document.createElement('div');
  render(<Counter />, container);
  
  const button = container.querySelector('button');
  const span = container.querySelector('span');
  
  expect(span.textContent).toBe('0');
  
  button.click();
  rerender(); // Flush the render
  
  expect(span.textContent).toBe('1');
});
```

**How it works**:

```javascript theme={null}
export function setupRerender() {
  options.__test__previousDebounce = options.debounceRendering;
  options.debounceRendering = cb => (options.__test__drainQueue = cb);
  return () => options.__test__drainQueue && options.__test__drainQueue();
}
```

Source: `test-utils/src/index.js:7-11`

It replaces `options.debounceRendering` to capture the render callback, then returns a function to execute it synchronously.

## act()

Runs a function and flushes all effects and rerenders. This is similar to React's `act()` helper.

```javascript theme={null}
import { act } from 'preact/test-utils';
import { render } from '@testing-library/preact';
import { useState, useEffect } from 'preact/hooks';

function AsyncCounter() {
  const [count, setCount] = useState(0);
  
  useEffect(() => {
    // Effect runs after render
    document.title = `Count: ${count}`;
  }, [count]);
  
  return (
    <button onClick={() => setCount(count + 1)}>
      {count}
    </button>
  );
}

test('effects run synchronously in act', async () => {
  const { container } = render(<AsyncCounter />);
  const button = container.querySelector('button');
  
  await act(() => {
    button.click();
  });
  
  // Effect has run, title is updated
  expect(document.title).toBe('Count: 1');
});
```

**Key features**:

1. Flushes all pending renders
2. Executes all pending effects (`useEffect`, `useLayoutEffect`)
3. Works with both sync and async callbacks
4. Returns a Promise

Source: `test-utils/src/index.js:27-112`

### Async Callbacks

`act()` works with asynchronous callbacks:

```javascript theme={null}
import { act } from 'preact/test-utils';

function DataLoader() {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(false);
  
  const loadData = async () => {
    setLoading(true);
    const result = await fetch('/api/data');
    const json = await result.json();
    setData(json);
    setLoading(false);
  };
  
  return (
    <div>
      <button onClick={loadData}>Load</button>
      {loading && <span>Loading...</span>}
      {data && <span>{data.value}</span>}
    </div>
  );
}

test('loading data', async () => {
  const { container } = render(<DataLoader />);
  const button = container.querySelector('button');
  
  await act(async () => {
    button.click();
    await new Promise(resolve => setTimeout(resolve, 100));
  });
  
  expect(container.textContent).toContain('Loading...');
});
```

Source: `test-utils/src/index.js:37-46`, `test-utils/src/index.js:97-101`

### Nested act() Calls

Nested `act()` calls are supported:

```javascript theme={null}
await act(async () => {
  // Outer act
  button.click();
  
  await act(async () => {
    // Inner act - only outer flushes
    anotherButton.click();
  });
  
  // Effects flush once when outer act completes
});
```

Source: `test-utils/src/index.js:28-54`

## teardown()

Cleans up test utilities and resets Preact's internal state. Call this after each test:

```javascript theme={null}
import { teardown } from 'preact/test-utils';

afterEach(() => {
  teardown();
});
```

**What it does**:

1. Flushes any pending updates
2. Restores original `debounceRendering`
3. Cleans up internal test state

```javascript theme={null}
export function teardown() {
  if (options.__test__drainQueue) {
    // Flush any pending updates leftover by test
    options.__test__drainQueue();
    delete options.__test__drainQueue;
  }
  
  if (typeof options.__test__previousDebounce !== 'undefined') {
    options.debounceRendering = options.__test__previousDebounce;
    delete options.__test__previousDebounce;
  } else {
    options.debounceRendering = undefined;
  }
}
```

Source: `test-utils/src/index.js:117-130`

<Warning>
  Always call `teardown()` after tests to prevent state leaking between tests.
</Warning>

## Testing Patterns

### Basic Component Test

```javascript theme={null}
import { render } from '@testing-library/preact';
import { act, teardown } from 'preact/test-utils';

function Greeting({ name }) {
  return <div>Hello, {name}!</div>;
}

test('renders greeting', () => {
  const { container } = render(<Greeting name="World" />);
  expect(container.textContent).toBe('Hello, World!');
});

afterEach(() => {
  teardown();
});
```

### Testing State Changes

```javascript theme={null}
import { render } from '@testing-library/preact';
import { act, teardown } from 'preact/test-utils';
import { useState } from 'preact/hooks';

function Toggle() {
  const [on, setOn] = useState(false);
  return (
    <div>
      <span>{on ? 'ON' : 'OFF'}</span>
      <button onClick={() => setOn(!on)}>Toggle</button>
    </div>
  );
}

test('toggles state', async () => {
  const { container } = render(<Toggle />);
  const button = container.querySelector('button');
  const span = container.querySelector('span');
  
  expect(span.textContent).toBe('OFF');
  
  await act(() => {
    button.click();
  });
  
  expect(span.textContent).toBe('ON');
  
  await act(() => {
    button.click();
  });
  
  expect(span.textContent).toBe('OFF');
});

afterEach(() => {
  teardown();
});
```

### Testing Effects

```javascript theme={null}
import { render } from '@testing-library/preact';
import { act, teardown } from 'preact/test-utils';
import { useState, useEffect } from 'preact/hooks';

function DocumentTitle({ title }) {
  useEffect(() => {
    document.title = title;
    return () => {
      document.title = '';
    };
  }, [title]);
  
  return <div>{title}</div>;
}

test('sets document title', async () => {
  const { rerender, unmount } = render(<DocumentTitle title="Test" />);
  
  await act(() => {
    // Wait for effect
  });
  
  expect(document.title).toBe('Test');
  
  await act(() => {
    rerender(<DocumentTitle title="Updated" />);
  });
  
  expect(document.title).toBe('Updated');
  
  await act(() => {
    unmount();
  });
  
  expect(document.title).toBe('');
});

afterEach(() => {
  teardown();
});
```

### Testing Async Operations

```javascript theme={null}
import { render, waitFor } from '@testing-library/preact';
import { act, teardown } from 'preact/test-utils';
import { useState, useEffect } from 'preact/hooks';

function UserProfile({ userId }) {
  const [user, setUser] = useState(null);
  const [loading, setLoading] = useState(true);
  
  useEffect(() => {
    setLoading(true);
    fetch(`/api/users/${userId}`)
      .then(res => res.json())
      .then(data => {
        setUser(data);
        setLoading(false);
      });
  }, [userId]);
  
  if (loading) return <div>Loading...</div>;
  return <div>{user.name}</div>;
}

test('loads user data', async () => {
  // Mock fetch
  global.fetch = jest.fn(() =>
    Promise.resolve({
      json: () => Promise.resolve({ name: 'John Doe' })
    })
  );
  
  const { container } = render(<UserProfile userId={1} />);
  
  expect(container.textContent).toBe('Loading...');
  
  await waitFor(() => {
    expect(container.textContent).toBe('John Doe');
  });
  
  expect(fetch).toHaveBeenCalledWith('/api/users/1');
});

afterEach(() => {
  teardown();
});
```

### Testing Custom Hooks

```javascript theme={null}
import { renderHook, act } from '@testing-library/preact';
import { teardown } from 'preact/test-utils';
import { useState, useCallback } from 'preact/hooks';

function useCounter(initialValue = 0) {
  const [count, setCount] = useState(initialValue);
  
  const increment = useCallback(() => {
    setCount(c => c + 1);
  }, []);
  
  const decrement = useCallback(() => {
    setCount(c => c - 1);
  }, []);
  
  const reset = useCallback(() => {
    setCount(initialValue);
  }, [initialValue]);
  
  return { count, increment, decrement, reset };
}

test('useCounter hook', () => {
  const { result } = renderHook(() => useCounter(10));
  
  expect(result.current.count).toBe(10);
  
  act(() => {
    result.current.increment();
  });
  
  expect(result.current.count).toBe(11);
  
  act(() => {
    result.current.decrement();
  });
  
  expect(result.current.count).toBe(10);
  
  act(() => {
    result.current.reset();
  });
  
  expect(result.current.count).toBe(10);
});

afterEach(() => {
  teardown();
});
```

### Testing Context

```javascript theme={null}
import { render } from '@testing-library/preact';
import { createContext } from 'preact';
import { useContext } from 'preact/hooks';
import { teardown } from 'preact/test-utils';

const ThemeContext = createContext('light');

function ThemedButton() {
  const theme = useContext(ThemeContext);
  return <button className={theme}>Click me</button>;
}

test('uses context value', () => {
  const { container } = render(
    <ThemeContext.Provider value="dark">
      <ThemedButton />
    </ThemeContext.Provider>
  );
  
  const button = container.querySelector('button');
  expect(button.className).toBe('dark');
});

afterEach(() => {
  teardown();
});
```

### Testing Error Boundaries

```javascript theme={null}
import { render } from '@testing-library/preact';
import { Component } from 'preact';
import { teardown } from 'preact/test-utils';

class ErrorBoundary extends Component {
  state = { error: null };
  
  componentDidCatch(error) {
    this.setState({ error });
  }
  
  render() {
    if (this.state.error) {
      return <div>Error: {this.state.error.message}</div>;
    }
    return this.props.children;
  }
}

function BrokenComponent() {
  throw new Error('Something went wrong');
}

test('catches errors', () => {
  // Suppress console.error in test
  const spy = jest.spyOn(console, 'error').mockImplementation(() => {});
  
  const { container } = render(
    <ErrorBoundary>
      <BrokenComponent />
    </ErrorBoundary>
  );
  
  expect(container.textContent).toBe('Error: Something went wrong');
  
  spy.mockRestore();
});

afterEach(() => {
  teardown();
});
```

## Integration with Testing Library

Preact works seamlessly with @testing-library/preact:

```javascript theme={null}
import { render, screen, fireEvent, waitFor } from '@testing-library/preact';
import { teardown } from 'preact/test-utils';
import '@testing-library/jest-dom';

function SearchForm({ onSearch }) {
  const [query, setQuery] = useState('');
  
  const handleSubmit = (e) => {
    e.preventDefault();
    onSearch(query);
  };
  
  return (
    <form onSubmit={handleSubmit}>
      <input
        type="text"
        placeholder="Search..."
        value={query}
        onChange={(e) => setQuery(e.target.value)}
      />
      <button type="submit">Search</button>
    </form>
  );
}

test('search form', async () => {
  const handleSearch = jest.fn();
  render(<SearchForm onSearch={handleSearch} />);
  
  const input = screen.getByPlaceholderText('Search...');
  const button = screen.getByText('Search');
  
  fireEvent.input(input, { target: { value: 'preact' } });
  expect(input.value).toBe('preact');
  
  fireEvent.click(button);
  expect(handleSearch).toHaveBeenCalledWith('preact');
});

afterEach(() => {
  teardown();
});
```

## Vitest Configuration

Example Vitest setup for Preact:

```javascript theme={null}
// vitest.config.js
import { defineConfig } from 'vitest/config';

export default defineConfig({
  test: {
    environment: 'jsdom',
    setupFiles: ['./vitest.setup.js']
  },
  esbuild: {
    jsxFactory: 'h',
    jsxFragment: 'Fragment',
    jsxInject: `import { h, Fragment } from 'preact'`
  }
});
```

```javascript theme={null}
// vitest.setup.js
import { afterEach } from 'vitest';
import { teardown } from 'preact/test-utils';

afterEach(() => {
  teardown();
});
```

Source: `vitest.config.mjs` in Preact repo

## Jest Configuration

Example Jest setup for Preact:

```javascript theme={null}
// jest.config.js
module.exports = {
  testEnvironment: 'jsdom',
  setupFilesAfterEnv: ['<rootDir>/jest.setup.js'],
  transform: {
    '^.+\\.jsx?$': 'babel-jest'
  }
};
```

```javascript theme={null}
// jest.setup.js
import { afterEach } from '@jest/globals';
import { teardown } from 'preact/test-utils';

afterEach(() => {
  teardown();
});
```

```json theme={null}
// .babelrc
{
  "presets": [
    ["@babel/preset-env", { "targets": { "node": "current" } }]
  ],
  "plugins": [
    ["@babel/plugin-transform-react-jsx", {
      "pragma": "h",
      "pragmaFrag": "Fragment"
    }]
  ]
}
```

## Snapshot Testing

```javascript theme={null}
import { render } from '@testing-library/preact';
import { teardown } from 'preact/test-utils';

function Card({ title, description }) {
  return (
    <div className="card">
      <h2>{title}</h2>
      <p>{description}</p>
    </div>
  );
}

test('card snapshot', () => {
  const { container } = render(
    <Card title="Test" description="This is a test" />
  );
  
  expect(container.firstChild).toMatchSnapshot();
});

afterEach(() => {
  teardown();
});
```

## Best Practices

1. **Always use act()**: Wrap state updates and side effects in `act()` to ensure all updates are flushed:

```javascript theme={null}
// Good
await act(() => {
  button.click();
});

// Bad
button.click(); // Updates may not flush
```

2. **Call teardown()**: Always clean up after tests:

```javascript theme={null}
afterEach(() => {
  teardown();
});
```

3. **Test user behavior**: Focus on how users interact with your components:

```javascript theme={null}
// Good - testing user interaction
fireEvent.click(screen.getByText('Submit'));

// Bad - testing implementation details
expect(component.state.submitted).toBe(true);
```

4. **Use Testing Library queries**: Prefer accessible queries:

```javascript theme={null}
// Good
screen.getByRole('button', { name: 'Submit' });
screen.getByLabelText('Email');

// Less good
container.querySelector('button');
```

5. **Mock external dependencies**: Mock API calls, timers, etc.:

```javascript theme={null}
beforeEach(() => {
  jest.spyOn(global, 'fetch').mockResolvedValue({
    json: async () => ({ data: 'test' })
  });
});
```

6. **Test edge cases**: Don't just test the happy path:

```javascript theme={null}
test('handles empty list', () => {
  const { container } = render(<List items={[]} />);
  expect(container.textContent).toBe('No items');
});

test('handles network error', async () => {
  fetch.mockRejectedValue(new Error('Network error'));
  const { container } = render(<DataLoader />);
  await waitFor(() => {
    expect(container.textContent).toContain('Error');
  });
});
```

## Debugging Tests

### Using debug()

```javascript theme={null}
import { render, screen } from '@testing-library/preact';

test('debug output', () => {
  const { debug } = render(<MyComponent />);
  debug(); // Prints current DOM
  
  // Or debug specific element
  debug(screen.getByRole('button'));
});
```

### Log render count

```javascript theme={null}
import { options } from 'preact';

test('component render count', () => {
  let renderCount = 0;
  const oldRender = options._render;
  options._render = (vnode) => {
    if (vnode.type === MyComponent) renderCount++;
    if (oldRender) oldRender(vnode);
  };
  
  // ... test code
  
  expect(renderCount).toBe(1);
  
  // Restore
  options._render = oldRender;
});
```

<Tip>
  Use `preact/debug` in tests for better error messages and validation.
</Tip>

## Common Pitfalls

### Forgetting to await act()

```javascript theme={null}
// Wrong
act(() => {
  button.click();
});
expect(span.textContent).toBe('1'); // May fail!

// Correct
await act(() => {
  button.click();
});
expect(span.textContent).toBe('1'); // Works!
```

### Not cleaning up

```javascript theme={null}
// Wrong - state leaks between tests
test('test 1', () => { /* ... */ });
test('test 2', () => { /* ... */ });

// Correct
afterEach(() => {
  teardown();
});
```

### Testing implementation details

```javascript theme={null}
// Wrong - brittle
expect(wrapper.find('div').at(2).hasClass('active')).toBe(true);

// Correct - tests behavior
expect(screen.getByRole('tab', { name: 'Settings' })).toHaveAttribute('aria-selected', 'true');
```
