WEB ATELIER (UDIT) Β· Learning by doing, with theory, practice and shared reflection

Testing: Building Confidence in Your Code

Draft

URL: https://ruvebal.github.io/web-atelier-udit/lessons/en/react/react-testing/

πŸ“‹ Table of Contents

β€œTests are not about finding bugs. Tests are about building confidence to change.”


🎯 Sprint Goal

By the end of this sprint: Your app has a testing foundationβ€”unit tests for logic, component tests for UI behavior, and at least one end-to-end test for a critical user flow.


πŸ“ Position in Journey

Sprint Focus Your App Grows
9. Backend Data fetching Real data, real app
10. Auth Security User sessions
β†’ 11. Testing Quality Reliable codebase
12. Performance Speed Optimized experience

🧭 Learning Objectives

By the end of this lesson, you will:

  • Write unit tests for pure functions and hooks
  • Test React components with React Testing Library
  • Mock API calls in component tests
  • Write at least one Cypress E2E test
  • Understand the Testing Trophy (what to test most)
  • Set up CI to run tests on every push

πŸ—οΈ The Testing Trophy

                    β”Œβ”€β”€β”€β”€β”€β”€β”€β”
                    β”‚  E2E  β”‚  ← Few, critical paths
                    β””β”€β”€β”€β”¬β”€β”€β”€β”˜
                   β”Œβ”€β”€β”€β”€β”΄β”€β”€β”€β”€β”
                   β”‚Integrationβ”‚  ← Most of your tests
                   β””β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”˜
                 β”Œβ”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”
                 β”‚    Unit     β”‚  ← Pure functions, hooks
                 β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”˜

Focus: Does the user's goal get accomplished?
Avoid: Testing implementation details

πŸ”§ Testing Stack

Type Tool Tests What
Unit Vitest Pure functions, utilities
Component React Testing Library User interactions with UI
Integration RTL + MSW Components with mocked APIs
E2E Cypress Full app flows in browser

πŸŽ“ Methodology: Atelier Practice

The Sprint Rhythm

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ DAY 1: Unit & Component Tests                           β”‚
β”‚   β€’ Set up Vitest and React Testing Library             β”‚
β”‚   β€’ Write unit tests for utility functions              β”‚
β”‚   β€’ Test a form component (render, type, submit)        β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ DAY 2: Integration & Mocking                            β”‚
β”‚   β€’ Set up MSW (Mock Service Worker) for API mocking    β”‚
β”‚   β€’ Test a data-fetching component end-to-end           β”‚
β”‚   β€’ Test error and loading states                       β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ DAY 3: E2E & CI                                         β”‚
β”‚   β€’ Write one Cypress test for login β†’ dashboard flow   β”‚
β”‚   β€’ Set up GitHub Actions to run tests on push          β”‚
β”‚   β€’ Celebrate green checkmarks βœ…                       β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

What to Test (Priority Order)

Priority Test This Example
πŸ”΄ High User can complete critical flow Login, checkout, create post
🟠 Medium Component handles states Loading, error, empty, success
🟑 Lower Edge cases Very long text, special characters
βšͺ Skip Implementation details Internal state shape, CSS classes

AI-Assisted Development Protocol

Task AI Role Your Role
Generate test cases Scaffold test structure Add assertions that matter
Mock complex APIs Create MSW handlers Verify they match real API
Debug failing tests Explain the error Understand why it fails
Increase coverage Suggest edge cases Prioritize important paths

πŸ“ Sprint Deliverables

  • 5+ unit tests for utilities and pure functions
  • 3+ component tests using RTL
  • MSW setup for mocking API in tests
  • 1 Cypress E2E test for critical flow
  • GitHub Actions workflow running tests on push
  • Test coverage report (aim for 60%+ on core code)
  • Reflection: What did tests reveal about your code?

πŸ’‘ Test Examples

Unit Test (Vitest)

// src/utils/formatPrice.test.js
import { formatPrice } from './formatPrice';

describe('formatPrice', () => {
  it('formats whole numbers', () => {
    expect(formatPrice(1000)).toBe('$10.00');
  });

  it('handles zero', () => {
    expect(formatPrice(0)).toBe('$0.00');
  });
});

Component Test (RTL)

// src/components/LoginForm.test.jsx
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { LoginForm } from './LoginForm';

test('submits email and password', async () => {
  const handleSubmit = vi.fn();
  render(<LoginForm onSubmit={handleSubmit} />);

  await userEvent.type(screen.getByLabelText(/email/i), 'test@example.com');
  await userEvent.type(screen.getByLabelText(/password/i), 'password123');
  await userEvent.click(screen.getByRole('button', { name: /login/i }));

  expect(handleSubmit).toHaveBeenCalledWith({
    email: 'test@example.com',
    password: 'password123'
  });
});

πŸ”— Lesson Navigation

Previous Current Next
Authentication Testing Performance

πŸ“š Key Concepts Preview

What β€œgood testing” means (in this course)

  • Tests are a change-enabler: the goal is confidence to refactor, not β€œ100% coverage”.
  • Prefer integration tests for user-visible behavior (forms, flows, navigation).
  • Unit test pure logic (reducers, validators, formatters).
  • Avoid brittle tests (testing implementation details, internal state, DOM structure).
  • Unit / component: Vitest + React Testing Library
  • Network mocking: MSW (Mock Service Worker)
  • E2E smoke (optional): Cypress (or Playwright if you already know it)

Example: test behavior, not structure

import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';

it('submits the form with user input', async () => {
  const user = userEvent.setup();
  const onSubmit = vi.fn();
  render(<LoginForm onSubmit={onSubmit} />);

  await user.type(screen.getByLabelText(/email/i), 'test@example.com');
  await user.type(screen.getByLabelText(/password/i), 'password123');
  await user.click(screen.getByRole('button', { name: /login/i }));

  expect(onSubmit).toHaveBeenCalledWith({ email: 'test@example.com', password: 'password123' });
});

Reflection (Atelier)

πŸ’­ Which bug did your tests preventβ€”specifically? What changed in your code because tests existed?

πŸ’­ Which test became too hard to write? What does that reveal about your architecture?

Koan

β€œIf your tests require lies, your design is already lying.”


β€œWrite tests. Not too many. Mostly integration.” β€” Guillermo Rauch