vitest-react-profiler
v1.12.0
Published
Performance testing utilities for React components and hooks with sync/async update tracking in Vitest
Maintainers
Readme
vitest-react-profiler
React component render tracking and performance testing utilities for Vitest
📖 Documentation • 🚀 Quick Start • 📚 API Reference • 💬 Discussions
Features
- 🔍 Precise Render Tracking - Count exact number of renders with zero guesswork
- ⚡ Performance Monitoring - Detect unnecessary re-renders and track component behavior
- 🎯 Phase Detection - Distinguish between mount, update, and nested update phases
- 📸 Snapshot API - Create render baselines with
snapshot()and measure deltas with Extended Matchers - 🪝 Hook Profiling - Profile custom hooks with full Context support via
wrapperoption - ⏱️ Async Testing - Subscribe to renders with
onRender()and wait withwaitForNextRender() - 🔔 Real-Time Notifications - React to renders immediately with event-based subscriptions
- ⚛️ React 18+ Concurrent Ready - Full support for
useTransitionanduseDeferredValue - 🧹 True Automatic Cleanup - Zero boilerplate! Components auto-clear between tests
- 🚀 Zero Config - Works out of the box with Vitest and React Testing Library
- 🛡️ Built-in Safety Mechanisms - Automatic detection of infinite render loops and memory leaks
- 💪 Full TypeScript Support - Complete type safety with custom Vitest matchers
- 🧬 Battle-Tested Quality - 100% mutation score, property-based testing, stress tests, SonarCloud verified.
- 🔬 Mathematically Verified - 266 property tests with 140,000+ randomized scenarios per run
- 🏋️ Stress-Tested - 34 stress tests validate performance on 10,000-render histories
- 📊 Performance Baselines - 46 benchmarks establish regression detection metrics
👥 Who Is This For?
🎨 UI-Kit and Design System Developers
Building a UI-kit for your project or company? You need to track, measure, and improve component performance. This tool helps you:
- Catch unnecessary re-renders during development
- Set performance budgets for components
- Document performance characteristics in tests
📦 Open Source React Library Maintainers
Publishing React components? It's critical to prove your solution is optimized and won't degrade performance in user projects. With this tool, you can:
- Add performance tests to CI/CD pipelines
- Showcase performance metrics in documentation
- Track performance regressions between releases
📊 Teams with Strict Performance SLAs
Have strict performance requirements (fintech, healthcare, real-time systems)? The tool allows you to:
- Set thresholds for render counts
- Automatically verify SLA compliance in tests
- Track asynchronous state updates
Quick Start
Installation
npm install --save-dev vitest-react-profiler
# or
yarn add -D vitest-react-profiler
# or
pnpm add -D vitest-react-profilerSetup
// vitest-setup.ts
import "vitest-react-profiler"; // Auto-registers afterEach cleanupConfigure Vitest:
// vitest.config.ts
import { defineConfig } from "vitest/config";
export default defineConfig({
test: {
environment: "jsdom",
setupFiles: ["./vitest-setup.ts"],
},
});Your First Test
import { render } from '@testing-library/react';
import { withProfiler } from 'vitest-react-profiler';
import { MyComponent } from './MyComponent';
it('should render only once on mount', () => {
const ProfiledComponent = withProfiler(MyComponent);
render(<ProfiledComponent />);
expect(ProfiledComponent).toHaveRenderedTimes(1);
expect(ProfiledComponent).toHaveMountedOnce();
});⏱️ Async Testing
Test components with asynchronous state updates using event-based utilities.
const AsyncComponent = () => {
const [data, setData] = useState(null);
useEffect(() => {
fetchData().then(setData);
}, []);
return <div>{data ?? "Loading..."}</div>;
};
it('should handle async updates', async () => {
const Profiled = withProfiler(AsyncComponent);
render(<Profiled />);
// Wait for mount + async update
await expect(Profiled).toEventuallyRenderTimes(2);
});Key Matchers
toEventuallyRenderTimes(n)- Wait for exact render counttoEventuallyRenderAtLeast(n)- Wait for minimum renderstoEventuallyReachPhase(phase)- Wait for specific phase
🎯 Stabilization API (v1.12.0)
Wait for components to "stabilize" - useful for virtualized lists, debounced search, and animations.
Key Methods
waitForStabilization(options)- Wait for renders to stop (debounce pattern)toEventuallyStabilize(options)- Matcher version for cleaner assertions
🪝 Hook Profiling
Profile custom hooks with full Context support.
import { profileHook } from 'vitest-react-profiler';
const useCounter = (initial: number) => {
const [count, setCount] = useState(initial);
return { count, increment: () => setCount(c => c + 1) };
};
it('should track hook renders', () => {
const { result, profiler } = profileHook(() => useCounter(0));
expect(profiler).toHaveRenderedTimes(1);
act(() => result.current.increment());
expect(profiler).toHaveRenderedTimes(2);
expect(result.current.count).toBe(1);
});With Context Support
const { result, profiler } = profileHook(() => useTheme(), {
wrapper: ({ children }) => (
<ThemeProvider theme="dark">{children}</ThemeProvider>
),
});⚛️ React 18+ Concurrent Features
Full support for React 18+ Concurrent rendering features - no special configuration needed!
The library automatically tracks renders from:
useTransition / startTransition
Test components using transitions for non-urgent updates
useDeferredValue
Test components using deferred values for performance optimization
How It Works
The library uses React's built-in <Profiler> API, which automatically handles Concurrent mode:
- ✅ Transitions are tracked as regular renders
- ✅ Deferred values trigger additional renders (as expected)
- ✅ Interrupted renders are handled correctly by React
- ✅ No special configuration or setup required
Note: The library tracks renders, not React's internal scheduling. Concurrent Features work transparently - your tests verify component behavior, not React internals.
📸 Snapshot API
Create render baselines and measure deltas for optimization testing.
Testing Single Render Per Action
const ProfiledCounter = withProfiler(Counter);
render(<ProfiledCounter />);
ProfiledCounter.snapshot(); // Create baseline
fireEvent.click(screen.getByText('Increment'));
expect(ProfiledCounter).toHaveRerenderedOnce(); // Verify single rerenderTesting React.memo Effectiveness
const ProfiledList = withProfiler(MemoizedList);
const { rerender } = render(<ProfiledList items={items} theme="light" />);
ProfiledList.snapshot();
rerender(<ProfiledList items={items} theme="dark" />);
expect(ProfiledList).toNotHaveRerendered(); // Memo prevented rerenderExtended Matchers
// Sync matchers
expect(ProfiledComponent).toHaveRerendered(); // At least one rerender
expect(ProfiledComponent).toHaveRerendered(3); // Exactly 3 rerenders
// Async matchers - wait for rerenders
await expect(ProfiledComponent).toEventuallyRerender();
await expect(ProfiledComponent).toEventuallyRerenderTimes(2, { timeout: 2000 });Key Methods & Matchers
| Method/Matcher | Description |
|----------------|-------------|
| snapshot() | Mark baseline for render counting |
| getRendersSinceSnapshot() | Get number of renders since baseline |
| toHaveRerenderedOnce() | Assert exactly one rerender |
| toNotHaveRerendered() | Assert no rerenders |
| toHaveRerendered() | Assert at least one rerender |
| toHaveRerendered(n) | Assert exactly n rerenders |
| toEventuallyRerender() | Wait for rerender |
| toEventuallyRerenderTimes(n) | Wait for exact count |
Documentation
📖 Full documentation is available in the Wiki
Quick Links
- Architecture Documentation - 📐 Complete technical architecture (15 sections, ~14,000 lines)
- Getting Started Guide - Installation and configuration
- API Reference - Complete API documentation
- Snapshot API - Extended matchers for optimization testing
- Hook Profiling - Testing React hooks
- React 18+ Concurrent Features - useTransition & useDeferredValue
- Examples - Real-world usage patterns
- Best Practices - Tips and recommendations
- Troubleshooting - Common issues and solutions
Contributing
We welcome contributions! Please read our Contributing Guide and Code of Conduct.
# Run tests
npm test # Unit/integration tests (811 tests)
npm run test:properties # Property-based tests (266 tests, 140k+ checks)
npm run test:stress # Stress tests (34 tests, large histories)
npm run test:bench # Performance benchmarks (46 benchmarks)
npm run test:mutation # Mutation testing (100% score)
# Build
npm run buildLicense
MIT © Oleg Ivanov
Made with ❤️ by the community
