Skip to main content
The setupRerender function configures Preact to render synchronously, making it easier to test components without dealing with asynchronous behavior.

Signature

function setupRerender(): () => void
Returns a function that manually triggers pending renders.

How it works

From test-utils/src/index.js:7-11:
export function setupRerender() {
  options.__test__previousDebounce = options.debounceRendering;
  options.debounceRendering = cb => (options.__test__drainQueue = cb);
  return () => options.__test__drainQueue && options.__test__drainQueue();
}
The function:
  1. Saves the original options.debounceRendering function
  2. Replaces it with a version that captures the render callback
  3. Returns a function that executes the captured callback
This allows you to control exactly when renders happen in your tests.

Usage

Basic setup

import { setupRerender, teardown } from 'preact/test-utils';
import { afterEach } from 'vitest';

const rerender = setupRerender();

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

test('my test', () => {
  // Render component
  render(<MyComponent />, container);
  
  // Manually flush renders
  rerender();
  
  // Now you can assert
  expect(container.textContent).toBe('Hello');
});

With state updates

import { useState } from 'preact/hooks';
import { setupRerender, teardown } from 'preact/test-utils';

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

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

Test suite setup

import { setupRerender, teardown } from 'preact/test-utils';
import { beforeEach, afterEach, test } from 'vitest';

let rerender;

beforeEach(() => {
  rerender = setupRerender();
});

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

test('test 1', () => {
  // Use rerender
});

test('test 2', () => {
  // Use rerender
});

When to use

Use setupRerender when:

  • You want fine-grained control over rendering
  • You’re not using act()
  • You’re writing low-level test utilities
  • You need to test render timing

Use act instead when:

  • You want automatic effect flushing
  • You’re testing user interactions
  • You’re testing async code
  • You want compatibility with React testing patterns
Most tests should use act() instead of setupRerender() directly. The act() function calls setupRerender() internally and also handles effects.

teardown function

Always call teardown() after using setupRerender() to restore Preact’s default behavior:
import { teardown } from 'preact/test-utils';

afterEach(() => {
  teardown();
});
From test-utils/src/index.js:117-130:
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;
  }
}
The teardown function:
  1. Flushes any pending renders
  2. Restores the original debounceRendering function
  3. Cleans up test state
Forgetting to call teardown() can cause test interference where one test’s render behavior affects another.

Example: Testing framework integration

import { setupRerender, teardown } from 'preact/test-utils';

export function setupPreactTests() {
  let rerender;
  
  beforeEach(() => {
    rerender = setupRerender();
  });
  
  afterEach(() => {
    teardown();
  });
  
  return {
    render(vnode, container) {
      render(vnode, container);
      rerender();
    },
    
    update(callback) {
      callback();
      rerender();
    }
  };
}

// Usage
const { render: renderSync, update } = setupPreactTests();

test('my test', () => {
  renderSync(<Component />, container);
  
  update(() => {
    button.click();
  });
  
  expect(container.textContent).toBe('Updated');
});

Best practices

Always clean up

// ✅ Good
afterEach(() => {
  teardown();
});

// ❌ Bad - will leak into other tests
const rerender = setupRerender();
// No cleanup!

Flush after renders

// ✅ Good
render(<Component />, container);
rerender();
expect(container.textContent).toBe('Hello');

// ❌ Bad - render may not be complete
render(<Component />, container);
expect(container.textContent).toBe('Hello');

Use act for effects

// ✅ Good for effects
await act(() => {
  render(<ComponentWithEffect />, container);
});

// ❌ Bad - setupRerender doesn't flush effects
const rerender = setupRerender();
render(<ComponentWithEffect />, container);
rerender(); // Effects not flushed!

Build docs developers (and LLMs) love