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:
- Saves the original
options.debounceRendering function
- Replaces it with a version that captures the render callback
- 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:
- Flushes any pending renders
- Restores the original
debounceRendering function
- 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!