Module 7: Testing Strategy

Vitest, React Testing Library, and Mocking.

1. The Testing Philosophy

In modern React, we test behavior, not implementation details. We don't care if a component has a state variable named `isOpen`. We care if the user can see the modal when they click the button.

Key Tools

  • Vitest: The runner (fast, compatible with Vite). Replaces Jest.
  • React Testing Library (RTL): The utility to render components and query the DOM like a user (`getByText`, `findByRole`).
  • MSW (Mock Service Worker): The industry standard for mocking network requests. It intercepts calls at the network layer.
TEST-101 To Do

Setup Test Infrastructure

User Story

As a developer, I need a robust testing environment to ensure my application is stable before deployment.

Acceptance Criteria
  • Install vitest, jsdom, and @testing-library/react.
  • Configure vite.config.ts to use the `jsdom` environment.
  • Add a test script to `package.json`.
Implementation Guide
npm install -D vitest jsdom @testing-library/react @testing-library/jest-dom

vite.config.ts:

/// 
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'

export default defineConfig({
  plugins: [react()],
  test: {
    environment: 'jsdom',
    setupFiles: './src/test/setup.ts',
    globals: true // optional, allows using describe/it without import
  }
})
TEST-102 To Do

Unit & Integration Tests

User Story

As a QA Engineer, I want to verify that individual components render correctly and respond to user interactions.

Acceptance Criteria
  • UserCard: Verify it renders the `name` and `role` props correctly.
  • Counter: Verify clicking "Increment" updates the count text.
  • Use screen.getByRole or screen.getByText for queries (accessible queries).
Code Example (UserCard.test.tsx)
import { render, screen } from '@testing-library/react';
import { describe, it, expect } from 'vitest';
import UserCard from './UserCard';

describe('UserCard', () => {
    it('renders user information correctly', () => {
        render(<UserCard name="Alice" role="Admin" />);
        
        // Check if text exists in the document
        expect(screen.getByText('Alice')).toBeInTheDocument();
        expect(screen.getByText(/admin/i)).toBeInTheDocument();
    });
});
Code Example (Counter.test.tsx)
import { render, screen, fireEvent } from '@testing-library/react';
import Counter from './Counter';

it('increments count on click', () => {
    render(<Counter />);
    
    const button = screen.getByRole('button', { name: /increment/i });
    
    // Initial state
    expect(screen.getByText('Count: 0')).toBeInTheDocument();
    
    // Interaction
    fireEvent.click(button);
    
    // Assert new state
    expect(screen.getByText('Count: 1')).toBeInTheDocument();
});
TEST-103 To Do

Async & Mocking (Advanced)

User Story

As a developer, I want to test data fetching components without relying on the real backend API.

Acceptance Criteria
  • Mock the fetch global or use MSW.
  • Verify the "Loading..." state appears initially.
  • Verify the data appears after the async operation completes using await screen.findBy....
Code Example (UserList.test.tsx)
import { render, screen, waitFor } from '@testing-library/react';
import { vi } from 'vitest';
import UserList from './UserList';

// Simple fetch mock
global.fetch = vi.fn();

function createFetchResponse(data) {
    return { json: () => new Promise((resolve) => resolve(data)) };
}

it('renders users after fetching', async () => {
    // Setup mock response
    (fetch as any).mockResolvedValue(createFetchResponse([
        { id: 1, name: 'John Doe' }
    ]));

    render(<UserList />);

    // 1. Check Loading State
    expect(screen.getByText(/loading/i)).toBeInTheDocument();

    // 2. Check Async Result (findBy waits for appearance)
    const userItem = await screen.findByText('John Doe');
    expect(userItem).toBeInTheDocument();
});
TEST-103 Mock API Calls

User Story: As a developer, I want to test my component's data fetching logic without hitting the real API.

Acceptance Criteria:

  • Use MSW (Mock Service Worker) to intercept requests.
  • Test the "Loading" state.
  • Test the "Success" state (data renders).
  • Test the "Error" state (error message renders).

2. Types of Testing

Unit Tests: Testing individual functions or components in isolation (e.g., `utils/formatDate.ts`).

Integration Tests: Testing how multiple units work together (e.g., `UserProfile` component fetching data and rendering `UserCard`).

E2E (End-to-End): Testing the full flow in a real browser (Playwright/Cypress). We will focus on Unit/Integration here.

🐛 Bug Hunt: The "Not Wrapped in Act" Warning

Scenario: You run a test and see a console error: Warning: An update to Component inside a test was not wrapped in act(...).

Why? Your component updated state (like after a fetch resolves) after your test finished or while your test was doing something else. React wants you to signal that an update is happening.

Fix: Usually, you don't need to use `act()` manually. Instead, use async queries like findByText which automatically wait for updates.

// ❌ Bad: Asserting immediately
// expect(screen.getByText('Data')).toBeInTheDocument(); // Fails, data isn't there yet

// ✅ Good: Waiting for appearance
// await screen.findByText('Data'); // Waits up to 1000ms (default)