Skip to content

greydragon888/vitest-react-profiler

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

129 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

vitest-react-profiler

npm version npm downloads CI

React React Testing Library Vitest

Enterprise Grade Testing Coverage Status Mutation testing badge Quality Gate Status Property-Based Testing

TypeScript Built with tsup ESLint Code Style: Prettier

License: MIT PRs Welcome Engineered with Claude Code

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 wrapper option
  • ⏱️ Async Testing - Subscribe to renders with onRender() and wait with waitForNextRender()
  • πŸ”” Real-Time Notifications - React to renders immediately with event-based subscriptions
  • βš›οΈ React 18+ Concurrent Ready - Full support for useTransition and useDeferredValue
  • 🧹 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-profiler

Setup

// vitest-setup.ts
import "vitest-react-profiler"; // Auto-registers afterEach cleanup

Configure 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 count
  • toEventuallyRenderAtLeast(n) - Wait for minimum renders
  • toEventuallyReachPhase(phase) - Wait for specific phase

πŸ“š Read the complete guide β†’


🎯 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

πŸ“š Read the complete guide β†’


πŸͺ 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>
  ),
});

πŸ“š Read the complete guide β†’


βš›οΈ 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.

πŸ“š Read the complete guide β†’


πŸ“Έ 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 rerender

Testing 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 rerender

Extended 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

πŸ“š Read the complete guide β†’


Documentation

πŸ“– Full documentation is available in the Wiki

Quick Links


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 build

License

MIT Β© Oleg Ivanov


Made with ❀️ by the community

Report Bug β€’ Request Feature β€’ Discussions

About

React component render tracking and performance testing utilities for Vitest

Resources

License

Code of conduct

Contributing

Security policy

Stars

Watchers

Forks

Sponsor this project

Contributors