πŸ§ͺTesting and Performance

Build reliable applications with testing strategies and performance monitoring.

🧀 Unit and Integration Testing

Welcome to Unit and Integration Testing with React! Writing tests ensures your components, hooks, and logic behave as expected. We'll use Jest and the React Testing Library to build a robust testing strategy.

πŸ’‘ Why Test?

  • Catch bugs early before they affect users
  • Ensure code changes don't break existing functionality
  • Document your component's behavior
  • Speed up development with automated tests

πŸ’‘ Introduction to Jest and React Testing Library

Jest is a popular testing framework that provides an easy-to-use API for writing tests. The React Testing Library offers utilities specifically for testing React components.

// Sample test file
import { render, screen } from '@testing-library/react';
import MyComponent from './MyComponent';

test('renders component with correct text', () => {
  render(<MyComponent />);
  expect(screen.getByText(/Hello World/)).toBeInTheDocument();
});

βœ… Writing Effective Unit Tests

Unit tests focus on individual components or functions in isolation. Write tests for different scenarios including: default state, user interactions, and edge cases.

// Testing component with state
import { render, fireEvent } from '@testing-library/react';

test('toggles state when clicked', () => {
  const { getByRole } = render(<ToggleButton />);
  const button = getByRole('button');
  
  expect(button).toHaveTextContent(/Off/);
  fireEvent.click(button);
  expect(button).toHaveTextContent(/On/);
});

πŸ’‘ Integration Testing with Mocks

Integration tests verify components work together. Use mocks to simulate dependencies like APIs or external services.

// Mocking API calls
jest.mock('../api');

const mockApiResponse = {
  data: { name: 'Test' }
};

api.fetchData.mockResolvedValue(mockApiResponse);

βœ… Advanced Testing Techniques

  • Test async logic with promises and timers
  • Use setup functions for shared test logic
  • Leverage testing utilities like matchers and selectors
  • Test accessibility and user experience
// Testing async behavior
import { act, render } from '@testing-library/react';

it('loads data asynchronously', async () => {
  const { getByText } = render(<AsyncComponent />);
  
  expect(getByText(/Loading/)).toBeInTheDocument();
  
  await act(async () => {
    // Simulate async operation
    await new Promise(resolve => setTimeout(resolve, 100));
  });
  
  expect(getByText(/Loaded/)).toBeInTheDocument();
});

πŸ’‘ Best Practices for Maintainable Tests

  • Write tests before implementation (TDD)
  • Keep tests focused and independent
  • Use descriptive test names
  • Maintain code coverage with CI/CD pipelines
  • Refactor tests along with production code

πŸ’‘ Real-World Testing Strategy

Start small and gradually add tests to your project. Focus on critical paths and components that are most likely to break.

🧠 Performance Optimization

Optimizing React applications is crucial for delivering smooth user experiences. In this section, we'll explore how to identify performance bottlenecks and implement effective optimizations.

πŸ’‘ Identifying Performance Issues with the Profiler

The React DevTools Profiler is a powerful tool for analyzing performance. It helps you identify slow components, excessive re-renders, and inefficient state updates.

// Open the Profiler in React DevTools
// Analyze render times and component trees

βœ… Optimizing Component Renders

  • Use React.memo to prevent unnecessary re-renders of pure components.
  • Avoid excessive state updates and unnecessary props changes.
const MemoizedComponent = React.memo(() => {
  // Component code
});

βœ… Memoizing Expensive Operations

Use useMemo to memoize expensive calculations and avoid redundant computations on every render.

const optimizedValue = useMemo(() => {
  // Expensive calculation
}, [deps]);

βœ… Stabilizing References with useCallback

The useCallback hook allows you to stabilize function references and prevent unnecessary re-renders of dependent components.

const memoizedFunction = useCallback(() => {
  // Function logic
}, [deps]);

πŸ’‘ Code-Splitting and Lazy Loading

Lazy loading improves initial load times by only loading components when they're needed.

// Implement code-splitting with React.lazy
const LazyComponent = React.lazy(() => import('./Component'));

// Use Suspense for fallback UI
<React.Suspense fallback={<div>Loading...</div>}>
  <LazyComponent />
</React.Suspense>

❌ Avoiding Common Performance Pitfalls

  • Don't overuse memoization or useCallback without understanding the dependency array.
  • Avoid deeply nested component trees that can slow down renders.
  • Don't use unnecessary inline functions as props, which can cause re-renders.

πŸ’‘ Best Practices for Performance Optimization

  • Always analyze performance with the Profiler before optimizing.
  • Optimize only when necessary and measure the impact of changes.
  • Consider using state management patterns like Redux or Recoil for complex applications.

Quiz

Question 1 of 10

What is the main purpose of unit testing?

  • Test individual components in isolation
  • Verify integration with external services
  • Ensure production environment stability
  • All of the above