Mahamudul Hasan Rubel
HomeBlogCoursesAboutProjectsSkillsExperiencePhotosContact
Mahamudul Hasan Rubel

Senior Software Engineer crafting high-performance web applications and SaaS platforms.

Navigation

  • Home
  • Blog
  • Courses
  • About
  • Projects
  • Skills
  • Experience
  • Photos
  • Contact

Get in Touch

Available for senior/lead roles and consulting.

bd.mhrubel@gmail.comHire Me

Subscribe to the newsletter

Get new articles and course lessons delivered to your inbox. No spam, unsubscribe anytime.

© 2026 Mahamudul Hasan Rubel. All rights reserved.

Built with using Next.js 16 & Tailwind v4

Back to Blog
Lesson 32 of the Advanced React: Performance, Architecture & Patterns course
ReactJune 28, 20264 min read

Testing Performance-Critical Components: Ensuring Speed with Jest

Learn to test React component performance by verifying re-render counts, memoization efficiency, and network resilience using Jest and React Testing Library.

ReactPerformanceTestingJestTesting Libraryjavascriptfrontend

Previously in this course, we explored Advanced Hook Patterns and how to manage global state with Zustand and Redux. While those lessons focused on architecture, this lesson focuses on stability: how to ensure your performance optimizations don't regress over time.

Performance testing in React isn't just about Lighthouse scores; it's about preventing "death by a thousand cuts" where small, unintentional re-renders degrade user experience in production.

Testing for Re-render Counts

To catch performance regressions, we need to assert that components only re-render when they absolutely have to. By default, React Testing Library (RTL) doesn't track render counts, but we can easily inject a spy into a component to monitor its lifecycle.

The Spy Pattern

We can pass a renderCount prop or use a simple jest.fn() wrapper to track how many times a component function executes.

JAVASCRIPT
// ExpensiveComponent.test.js
import { render } from CE9178">'@testing-library/react';

const renderSpy = jest.fn();

const MyComponent = ({ data }) => {
  renderSpy();
  return <div>{data.name}</div>;
};

test(CE9178">'should only render once when parent state changes unrelatedly', () => {
  const { rerender } = render(<MyComponent data={{ name: CE9178">'Test' }} />);
  expect(renderSpy).toHaveBeenCalledTimes(1);
  
  // Simulate parent update with same data reference
  rerender(<MyComponent data={{ name: CE9178">'Test' }} />);
  
  // If this fails, your component is re-rendering unnecessarily
  expect(renderSpy).toHaveBeenCalledTimes(1); 
});

Verifying Memoization

Memoization is a double-edged sword. If you use React.memo or useMemo incorrectly, you might introduce memory leaks or fail to update the UI when needed. To verify your memoization strategy, we test that the component remains "stable" when props are shallow-equal.

The Memoization Test Suite

When testing memoization, always test two scenarios: one where the prop reference stays the same, and one where it changes.

ScenarioExpected Render CountWhy?
Initial Render1Component must mount.
Same Prop Ref1React.memo should bail out.
New Prop Ref2Component must update.
JAVASCRIPT
test(CE9178">'memoized component avoids re-render on stable props', () => {
  const stableData = { id: 1 };
  const { rerender } = render(<MemoizedComponent data={stableData} />);
  expect(renderSpy).toHaveBeenCalledTimes(1);

  // Still same reference
  rerender(<MemoizedComponent data={stableData} />);
  expect(renderSpy).toHaveBeenCalledTimes(1);

  // New reference
  rerender(<MemoizedComponent data={{ id: 1 }} />);
  expect(renderSpy).toHaveBeenCalledTimes(2);
});

Simulating Slow Network Conditions

Performance-critical components often deal with async data. If your component doesn't handle loading states gracefully, it can lead to layout shifts or "jank." We simulate slow networks by delaying our service mocks.

Implementing Controlled Latency

Using jest.mock, we can introduce an artificial delay to our API layer to ensure our Suspense boundaries or loading skeletons trigger correctly.

JAVASCRIPT
// api.js
export const fetchData = () => fetch(CE9178">'/data').then(res => res.json());

// api.test.js
jest.mock(CE9178">'./api', () => ({
  fetchData: jest.fn(() => 
    new Promise(resolve => setTimeout(() => resolve({ data: CE9178">'fast' }), 1000))
  )
}));

test(CE9178">'shows loading skeleton during slow network', async () => {
  const { getByTestId } = render(<DataComponent />);
  
  // Assert loading state is present immediately
  expect(getByTestId(CE9178">'skeleton')).toBeInTheDocument();
  
  // Wait for the slow promise to resolve
  await waitForElementToBeRemoved(() => getByTestId(CE9178">'skeleton'));
  expect(getByTestId(CE9178">'content')).toHaveTextContent(CE9178">'fast');
});

Hands-on Exercise

In our running project, we have a DashboardGrid component that fetches widgets.

  1. Create a test file for DashboardGrid.
  2. Mock the API call to resolve in 500ms.
  3. Assert that the DashboardGrid component renders a LoadingSpinner immediately and replaces it with the grid once the promise resolves.
  4. Verify that the grid does not re-render if the user toggles a non-related UI state (like a dark-mode toggle).

Common Pitfalls

  • Over-testing memoization: Don't test every single component for memoization. Only test those that sit high in the tree or render frequently.
  • Assuming Jest is the browser: Jest runs in JSDOM, which doesn't have a layout engine. It cannot measure actual paints or paint times. Use Cypress or Playwright for visual/paint-based performance tests.
  • The "Double-Render" trap: React 18's Strict Mode causes components to render twice in development. If your tests run in an environment mimicking Strict Mode, your toHaveBeenCalledTimes assertions will be off by one.

Recap

Performance testing is about intent. By using spies to track renders and controlling the timing of async mocks, you create a safety net that ensures your Introduction to Testing efforts actually translate into a performant user experience. Keep your tests focused on the critical path, and remember that Testing Hooks and Components is the foundation upon which these performance assertions are built.

Up next: We will explore Static Site Generation (SSG) patterns and how they change our approach to initial page-load performance.

Previous lessonManaging Global State with Zustand/ReduxNext lesson Static Site Generation (SSG) Patterns
Back to Blog

Similar Posts

ReactArchitectureJune 28, 20263 min read

Internationalization (i18n) Architecture: Performance at Scale

Learn to architect performant i18n in React. Implement lazy-loaded translations, optimize re-renders during locale switches, and manage locale state efficiently.

Read more
ReactJune 28, 20264 min read

Static Site Generation (SSG) Patterns: Architecting for Performance

Part of the course

Advanced React: Performance, Architecture & Patterns

advanced · Lesson 32 of 47

  1. 1

    Deep Dive into the Reconciliation Algorithm

    4 min
  2. 2

    Profiling with React DevTools

    3 min
  3. 3

    Establishing Performance Budgets

    3 min

Master Static Site Generation (SSG) and Incremental Static Regeneration (ISR) to shift rendering to build time and deliver lightning-fast, scalable React apps.

Read more
ReactJune 28, 20264 min read

Final Project Audit & Optimization: Achieving Production Readiness

Master the final project audit and optimization phase. Learn to compare performance metrics against your baseline and finalize your app for production readiness.

Read more
  • 4

    Strategic use of React.memo

    3 min
  • 5

    Mastering useCallback and useMemo

    4 min
  • 6

    State Colocation Strategies

    4 min
  • 7

    Optimizing Context Providers

    4 min
  • 8

    Advanced Context Composition

    4 min
  • 9

    Eliminating Prop Drilling

    4 min
  • 10

    Introduction to Concurrent React

    4 min
  • 11

    Non-blocking UI with useTransition

    4 min
  • 12

    Handling Deferred Data with useDeferredValue

    3 min
  • 13

    Mastering Suspense for Data Fetching

    4 min
  • 14

    Streaming Server-Side Rendering

    3 min
  • 15

    Designing Compound Components

    3 min
  • 16

    The Render Props Pattern

    4 min
  • 17

    Implementing Control Props

    4 min
  • 18

    Headless UI Architectures

    3 min
  • 19

    Modular Directory Structures

    3 min
  • 20

    Refactoring Monolithic Components

    3 min
  • 21

    Optimistic UI Updates

    3 min
  • 22

    Advanced Cache Invalidation

    4 min
  • 23

    Handling Race Conditions

    4 min
  • 24

    Server-Client State Synchronization

    3 min
  • 25

    Route-level Code Splitting

    4 min
  • 26

    Offloading Tasks with Web Workers

    3 min
  • 27

    Advanced Error Boundaries

    3 min
  • 28

    Monitoring Production Performance

    4 min
  • 29

    Final Project Audit & Optimization

    4 min
  • 30

    Advanced Hook Patterns

    3 min
  • 31

    Managing Global State with Zustand/Redux

    4 min
  • 32

    Testing Performance-Critical Components

    4 min
  • 33

    Static Site Generation (SSG) Patterns

    4 min
  • 34

    Internationalization (i18n) Architecture

    3 min
  • 35

    Accessibility (a11y) in Advanced Components

    4 min
  • 36

    Managing Third-Party Integrations

    Coming soon
  • 37

    Advanced Form Handling

    Coming soon
  • 38

    Using Portals for UI Overlays

    Coming soon
  • 39

    Implementing Virtualized Lists

    Coming soon
  • 40

    Building Design System Primitives

    Coming soon
  • 41

    Managing Large-Scale Data Fetching

    Coming soon
  • 42

    Micro-Frontends with React

    Coming soon
  • 43

    Security Best Practices in React

    Coming soon
  • 44

    Advanced Ref Usage

    Coming soon
  • 45

    Memoization Pitfalls

    Coming soon
  • 46

    Mastering React Patterns for Scalability

    Coming soon
  • 47

    Advanced TypeScript with React

    Coming soon
  • View full course