๐Ÿงช React Testing Applications

Jest, React Testing Library, Mocking, E2E Testing aur bahut kuch!

๐Ÿ“š Testing Kya Hai Aur Kyun Zaroori Hai?

Testing matlab apne code ko verify karna ki wo expected tarike se kaam kar raha hai ya nahi. React applications mein testing bahut important hai kyunki:

  • ๐Ÿ› Bugs ko early catch kar sakte hain
  • ๐Ÿ”’ Code quality maintain hoti hai
  • โ™ป๏ธ Refactoring safe ho jata hai
  • ๐Ÿ“– Tests documentation ka kaam bhi karte hain
  • ๐Ÿš€ Deployment confidence badhta hai
Testing Pyramid: Ek concept hai jo batata hai ki different levels of testing ka ratio kya hona chahiye. Neeche click karke explore karo!
๐Ÿ”บ Interactive Testing Pyramid - Click on Each Layer!
E2E Tests (10%)
Integration Tests (30%)
Unit Tests (60%)

๐ŸŽฏ End-to-End (E2E) Tests

Kya hai: Pura application ko user ki tarah test karna

Tools: Cypress, Playwright, Selenium

Example: Login โ†’ Dashboard โ†’ Create Post โ†’ Logout

Pros: Real user experience test hota hai

Cons: Slow aur expensive, maintenance zyada

๐Ÿ”— Integration Tests

Kya hai: Multiple components/modules ko saath mein test karna

Tools: React Testing Library, Jest

Example: Form component + validation + API call

Pros: Components ki interaction verify hoti hai

Cons: Unit tests se slow, setup complex

โšก Unit Tests

Kya hai: Individual functions/components ko isolation mein test karna

Tools: Jest, Vitest

Example: Single function, single component

Pros: Fast, easy to write, pinpoint issues

Cons: Integration issues miss ho sakte hain

๐Ÿƒ Jest Basics

Jest ek JavaScript testing framework hai jo Facebook ne banaya. Ye React ke saath out-of-the-box kaam karta hai aur bahut powerful features provide karta hai.

Jest Features: Zero config, snapshot testing, mocking, code coverage, parallel test execution

Basic Test Structure

// sum.js export function sum(a, b) { return a + b; } // sum.test.js import { sum } from './sum'; describe('sum function', () => { test('adds 1 + 2 to equal 3', () => { expect(sum(1, 2)).toBe(3); }); test('adds negative numbers', () => { expect(sum(-1, -2)).toBe(-3); }); });

describe: Test suite banane ke liye - related tests ko group karta hai

test/it: Individual test case define karne ke liye

expect: Assertion banane ke liye - kya expect kar rahe ho

Common Matchers

// Equality expect(value).toBe(4); // Strict equality (===) expect(value).toEqual(obj); // Deep equality // Truthiness expect(value).toBeTruthy(); expect(value).toBeFalsy(); expect(value).toBeNull(); expect(value).toBeUndefined(); // Numbers expect(value).toBeGreaterThan(3); expect(value).toBeLessThanOrEqual(5); expect(0.1 + 0.2).toBeCloseTo(0.3); // Strings expect('team').toMatch(/tea/); expect('team').not.toMatch(/I/); // Arrays expect(['apple', 'banana']).toContain('apple'); // Exceptions expect(() => throwError()).toThrow(); expect(() => throwError()).toThrow('error message');
๐ŸŽฎ Jest Test Simulator

Test Code

Test Results

โš›๏ธ React Testing Library

React Testing Library (RTL) ek library hai jo React components ko test karne ke liye banayi gayi hai. Iska philosophy hai: "Test your components the way users interact with them"

Matlab implementation details ko test karne ki jagah, user behavior ko test karo!

Core Principle: "The more your tests resemble the way your software is used, the more confidence they can give you."

Basic Component Testing

// Button.jsx export function Button({ onClick, children }) { return ( <button onClick={onClick}> {children} </button> ); } // Button.test.jsx import { render, screen, fireEvent } from '@testing-library/react'; import { Button } from './Button'; describe('Button Component', () => { test('renders button with text', () => { render(<Button>Click Me</Button>); const button = screen.getByText('Click Me'); expect(button).toBeInTheDocument(); }); test('calls onClick when clicked', () => { const handleClick = jest.fn(); render(<Button onClick={handleClick}>Click</Button>); const button = screen.getByText('Click'); fireEvent.click(button); expect(handleClick).toHaveBeenCalledTimes(1); }); });

render: Component ko DOM mein render karta hai

screen: Rendered output ko query karne ke liye

fireEvent: User events simulate karne ke liye

Query Methods

// getBy* - Element hona chahiye, nahi toh error screen.getByText('Submit'); screen.getByRole('button'); screen.getByLabelText('Email'); screen.getByPlaceholderText('Enter name'); screen.getByTestId('custom-element'); // queryBy* - Element nahi hai toh null return karta screen.queryByText('Not Found'); // null if not found // findBy* - Async elements ke liye (returns Promise) await screen.findByText('Loaded Data'); // getAllBy*, queryAllBy*, findAllBy* - Multiple elements screen.getAllByRole('listitem');
๐ŸŽฏ Query Method Selector

Sample Component:

Login Form

Select Query Method:

๐Ÿ”ฌ Unit Testing

Unit Testing mein hum individual functions aur components ko isolation mein test karte hain. Ye sabse chhote level ka testing hai aur sabse zyada tests bhi yahi hote hain.

Testing Pure Functions

// utils.js export function formatCurrency(amount) { return `โ‚น${amount.toFixed(2)}`; } export function validateEmail(email) { const regex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; return regex.test(email); } // utils.test.js import { formatCurrency, validateEmail } from './utils'; describe('formatCurrency', () => { test('formats number to currency', () => { expect(formatCurrency(100)).toBe('โ‚น100.00'); expect(formatCurrency(99.5)).toBe('โ‚น99.50'); }); }); describe('validateEmail', () => { test('validates correct email', () => { expect(validateEmail('test@example.com')).toBe(true); }); test('rejects invalid email', () => { expect(validateEmail('invalid')).toBe(false); expect(validateEmail('test@')).toBe(false); }); });

Testing React Hooks

// useCounter.js import { useState } from 'react'; export function useCounter(initialValue = 0) { const [count, setCount] = useState(initialValue); const increment = () => setCount(c => c + 1); const decrement = () => setCount(c => c - 1); const reset = () => setCount(initialValue); return { count, increment, decrement, reset }; } // useCounter.test.js import { renderHook, act } from '@testing-library/react'; import { useCounter } from './useCounter'; describe('useCounter', () => { test('initializes with default value', () => { const { result } = renderHook(() => useCounter()); expect(result.current.count).toBe(0); }); test('increments counter', () => { const { result } = renderHook(() => useCounter()); act(() => { result.current.increment(); }); expect(result.current.count).toBe(1); }); test('resets to initial value', () => { const { result } = renderHook(() => useCounter(5)); act(() => { result.current.increment(); result.current.reset(); }); expect(result.current.count).toBe(5); }); });

๐Ÿ”— Integration Testing

Integration Testing mein hum multiple components ya modules ko saath mein test karte hain. Ye verify karta hai ki different parts ek saath sahi se kaam kar rahe hain.

Testing Component Integration

// TodoApp.jsx import { useState } from 'react'; import TodoList from './TodoList'; import TodoForm from './TodoForm'; export function TodoApp() { const [todos, setTodos] = useState([]); const addTodo = (text) => { setTodos([...todos, { id: Date.now(), text, done: false }]); }; const toggleTodo = (id) => { setTodos(todos.map(todo => todo.id === id ? { ...todo, done: !todo.done } : todo )); }; return ( <div> <TodoForm onAdd={addTodo} /> <TodoList todos={todos} onToggle={toggleTodo} /> </div> ); } // TodoApp.test.jsx import { render, screen, fireEvent } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { TodoApp } from './TodoApp'; describe('TodoApp Integration', () => { test('adds and displays new todo', async () => { const user = userEvent.setup(); render(<TodoApp />); const input = screen.getByPlaceholderText('Add todo'); const button = screen.getByText('Add'); await user.type(input, 'Buy groceries'); await user.click(button); expect(screen.getByText('Buy groceries')).toBeInTheDocument(); }); test('toggles todo completion', async () => { const user = userEvent.setup(); render(<TodoApp />); // Add todo await user.type(screen.getByPlaceholderText('Add todo'), 'Test task'); await user.click(screen.getByText('Add')); // Toggle todo const checkbox = screen.getByRole('checkbox'); await user.click(checkbox); expect(checkbox).toBeChecked(); }); });

๐ŸŽญ Mocking APIs

Mocking ka matlab hai fake data ya functions create karna testing ke liye. API calls ko mock karna bahut important hai kyunki:

  • Tests fast chalte hain (no network calls)
  • Tests reliable hote hain (no external dependencies)
  • Different scenarios test kar sakte hain (success, error, loading)

Mocking with Jest

// api.js export async function fetchUser(id) { const response = await fetch(`/api/users/${id}`); return response.json(); } // UserProfile.jsx import { useState, useEffect } from 'react'; import { fetchUser } from './api'; export function UserProfile({ userId }) { const [user, setUser] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); useEffect(() => { fetchUser(userId) .then(data => { setUser(data); setLoading(false); }) .catch(err => { setError(err.message); setLoading(false); }); }, [userId]); if (loading) return <div>Loading...</div>; if (error) return <div>Error: {error}</div>; return <div>{user.name}</div>; } // UserProfile.test.jsx import { render, screen, waitFor } from '@testing-library/react'; import { UserProfile } from './UserProfile'; import * as api from './api'; jest.mock('./api'); describe('UserProfile', () => { test('displays loading state', () => { api.fetchUser.mockImplementation(() => new Promise(() => {})); render(<UserProfile userId={1} />); expect(screen.getByText('Loading...')).toBeInTheDocument(); }); test('displays user data on success', async () => { api.fetchUser.mockResolvedValue({ name: 'John Doe' }); render(<UserProfile userId={1} />); await waitFor(() => { expect(screen.getByText('John Doe')).toBeInTheDocument(); }); }); test('displays error on failure', async () => { api.fetchUser.mockRejectedValue(new Error('Failed to fetch')); render(<UserProfile userId={1} />); await waitFor(() => { expect(screen.getByText(/Error:/)).toBeInTheDocument(); }); }); });
๐ŸŽฎ API Mocking Simulator

Mock Configuration

API Response

MSW (Mock Service Worker)

// mocks/handlers.js import { rest } from 'msw'; export const handlers = [ rest.get('/api/users/:id', (req, res, ctx) => { const { id } = req.params; return res( ctx.status(200), ctx.json({ id, name: 'John Doe', email: 'john@example.com' }) ); }), rest.post('/api/login', (req, res, ctx) => { const { username, password } = req.body; if (username === 'admin' && password === 'password') { return res( ctx.status(200), ctx.json({ token: 'fake-jwt-token' }) ); } return res( ctx.status(401), ctx.json({ message: 'Invalid credentials' }) ); }) ]; // setupTests.js import { setupServer } from 'msw/node'; import { handlers } from './mocks/handlers'; const server = setupServer(...handlers); beforeAll(() => server.listen()); afterEach(() => server.resetHandlers()); afterAll(() => server.close());

MSW ek powerful tool hai jo network level pe mocking karta hai. Ye actual HTTP requests ko intercept karke mock responses return karta hai. Development aur testing dono mein use ho sakta hai!

๐Ÿ“ธ Snapshot Testing

Snapshot Testing mein component ka rendered output save kar lete hain aur future tests mein compare karte hain. Agar output change hota hai toh test fail ho jata hai.

Ye UI regressions catch karne ke liye bahut useful hai!

Creating Snapshots

// Card.jsx export function Card({ title, description, image }) { return ( <div className="card"> {image && <img src={image} alt={title} />} <h3>{title}</h3> <p>{description}</p> </div> ); } // Card.test.jsx import { render } from '@testing-library/react'; import { Card } from './Card'; describe('Card Component', () => { test('matches snapshot', () => { const { container } = render( <Card title="Test Card" description="This is a test" image="/test.jpg" /> ); expect(container.firstChild).toMatchSnapshot(); }); test('matches snapshot without image', () => { const { container } = render( <Card title="No Image Card" description="Card without image" /> ); expect(container.firstChild).toMatchSnapshot(); }); });
๐Ÿ“ธ Snapshot Comparison

Original Snapshot

<div class="card">
  <img src="/test.jpg" alt="Test" />
  <h3>Test Card</h3>
  <p>Description</p>
</div>
                            

New Snapshot

<div class="card">
  <img src="/test.jpg" alt="Test" />
  <span class="badge">New</span>
  <h3>Test Card</h3>
  <p>Description</p>
  <p class="desc">Description</p>
</div>
                            

Kab use karein: UI components jo frequently change nahi hote

Kab avoid karein: Dynamic content, timestamps, random data

Best Practice: Snapshots ko regularly review karo aur unnecessary changes ko avoid karo

๐ŸŽฌ E2E Testing (Playwright / Cypress)

End-to-End Testing mein pura application ko real user ki tarah test karte hain. Browser mein actual interactions simulate karte hain.

Cypress Example

// cypress/e2e/login.cy.js describe('Login Flow', () => { beforeEach(() => { cy.visit('http://localhost:3000/login'); }); it('successfully logs in with valid credentials', () => { // Type username cy.get('[data-testid="username"]').type('testuser'); // Type password cy.get('[data-testid="password"]').type('password123'); // Click login button cy.get('[data-testid="login-btn"]').click(); // Verify redirect to dashboard cy.url().should('include', '/dashboard'); // Verify welcome message cy.contains('Welcome, testuser').should('be.visible'); }); it('shows error with invalid credentials', () => { cy.get('[data-testid="username"]').type('wronguser'); cy.get('[data-testid="password"]').type('wrongpass'); cy.get('[data-testid="login-btn"]').click(); cy.contains('Invalid credentials').should('be.visible'); cy.url().should('include', '/login'); }); it('validates required fields', () => { cy.get('[data-testid="login-btn"]').click(); cy.contains('Username is required').should('be.visible'); cy.contains('Password is required').should('be.visible'); }); });

Playwright Example

// tests/login.spec.js import { test, expect } from '@playwright/test'; test.describe('Login Flow', () => { test.beforeEach(async ({ page }) => { await page.goto('http://localhost:3000/login'); }); test('successful login', async ({ page }) => { // Fill form await page.fill('[data-testid="username"]', 'testuser'); await page.fill('[data-testid="password"]', 'password123'); // Click login await page.click('[data-testid="login-btn"]'); // Wait for navigation await page.waitForURL('**/dashboard'); // Verify content await expect(page.locator('text=Welcome, testuser')).toBeVisible(); }); test('handles network errors', async ({ page }) => { // Simulate network failure await page.route('**/api/login', route => route.abort()); await page.fill('[data-testid="username"]', 'testuser'); await page.fill('[data-testid="password"]', 'password123'); await page.click('[data-testid="login-btn"]'); await expect(page.locator('text=Network error')).toBeVisible(); }); test('works on mobile viewport', async ({ page }) => { await page.setViewportSize({ width: 375, height: 667 }); await page.fill('[data-testid="username"]', 'testuser'); await page.fill('[data-testid="password"]', 'password123'); await page.click('[data-testid="login-btn"]'); await expect(page).toHaveURL(/.*dashboard/); }); });

Cypress vs Playwright

๐ŸŒฒ Cypress

  • Easy to setup aur use
  • Great developer experience
  • Time travel debugging
  • Automatic waiting
  • Real-time reloading
  • Sirf Chromium browsers

๐ŸŽญ Playwright

  • Multiple browsers support
  • Parallel execution
  • Better performance
  • Mobile emulation
  • Network interception
  • Auto-wait for elements

โœจ Testing Best Practices

1. Test Behavior, Not Implementation

โŒ Bad: Component ki internal state check karna

โœ… Good: User ko kya dikhta hai wo check karna

// โŒ Bad - Testing implementation test('counter state updates', () => { const { result } = renderHook(() => useCounter()); expect(result.current.count).toBe(0); // Internal state }); // โœ… Good - Testing behavior test('displays incremented count', () => { render(<Counter />); const button = screen.getByText('Increment'); fireEvent.click(button); expect(screen.getByText('Count: 1')).toBeInTheDocument(); });

2. Use Data-TestId Sparingly

Pehle accessible queries use karo (role, label, text), last resort mein testid

// Priority order: 1. getByRole('button', { name: 'Submit' }) 2. getByLabelText('Email') 3. getByPlaceholderText('Enter email') 4. getByText('Submit') 5. getByTestId('submit-button') // Last resort

3. Keep Tests Independent

Har test independent hona chahiye - ek test dusre pe depend nahi karna chahiye

4. Use Descriptive Test Names

Test name se hi samajh aana chahiye ki kya test ho raha hai

// โŒ Bad test('button works', () => { ... }); // โœ… Good test('submits form when submit button is clicked', () => { ... }); test('shows validation error when email is invalid', () => { ... });

5. Avoid Testing Third-Party Libraries

React, Redux, etc. already tested hain - apna code test karo

6. Mock External Dependencies

API calls, timers, random values - sab mock karo for consistent tests

7. Test Edge Cases

Sirf happy path nahi, error cases bhi test karo

  • Empty states
  • Loading states
  • Error states
  • Boundary values
  • Network failures

๐ŸŽฏ Interview Questions

1. Jest aur React Testing Library mein kya difference hai?
Easy โ–ผ

Answer:

Jest ek testing framework hai jo test runner, assertion library, aur mocking capabilities provide karta hai. Ye kisi bhi JavaScript code ko test kar sakta hai.

React Testing Library ek library hai jo specifically React components ko test karne ke liye banayi gayi hai. Ye DOM queries aur user interactions simulate karne ke tools provide karta hai.

Relationship: RTL Jest ke saath use hota hai - Jest tests run karta hai aur RTL React components ko render aur query karta hai.

// Jest provides: describe, test, expect, mock // RTL provides: render, screen, fireEvent, waitFor
2. getBy, queryBy, aur findBy mein kya difference hai?
Medium โ–ผ

Answer:

  • getBy*: Element immediately milna chahiye, nahi toh error throw karta hai. Synchronous hai.
  • queryBy*: Element nahi mila toh null return karta hai (no error). Negative assertions ke liye use hota hai.
  • findBy*: Async elements ke liye. Promise return karta hai aur element ke appear hone ka wait karta hai (default 1000ms timeout).
// Use getBy when element should be there const button = screen.getByRole('button'); // Use queryBy to check element doesn't exist expect(screen.queryByText('Error')).not.toBeInTheDocument(); // Use findBy for async elements const data = await screen.findByText('Loaded Data');
3. API calls ko test karte waqt kya approach use karni chahiye?
Medium โ–ผ

Answer:

API calls ko test karne ke liye mainly 3 approaches hain:

  1. Jest Mock Functions: API module ko mock kar do
    jest.mock('./api'); api.fetchUser.mockResolvedValue({ name: 'John' });
  2. MSW (Mock Service Worker): Network level pe mock karo
    rest.get('/api/users', (req, res, ctx) => { return res(ctx.json({ name: 'John' })); });
  3. Dependency Injection: API client ko prop ke through pass karo aur test mein mock version inject karo

Best Practice: MSW use karo kyunki ye most realistic hai aur development mein bhi use ho sakta hai.

4. Snapshot testing ke advantages aur disadvantages kya hain?
Medium โ–ผ

Advantages:

  • Quick to write - ek line mein test likh sakte ho
  • UI regressions catch kar leta hai
  • Component structure changes detect karta hai
  • Documentation ka kaam bhi karta hai

Disadvantages:

  • False positives - minor changes pe bhi fail ho jata hai
  • Developers blindly update kar dete hain without reviewing
  • Large snapshots maintain karna mushkil
  • Dynamic content (dates, IDs) ke saath problem
  • Actual behavior test nahi karta, sirf output compare karta hai

Best Practice: Snapshots ko supplement ki tarah use karo, main testing strategy nahi. Stable UI components ke liye best hai.

5. Integration tests aur E2E tests mein kya difference hai?
Easy โ–ผ

Integration Tests:

  • Multiple components/modules ko saath test karte hain
  • Still in isolation - external dependencies mock hote hain
  • Fast execution
  • Jest + RTL se run hote hain
  • Example: Form + validation + state management

E2E Tests:

  • Pura application flow test karte hain
  • Real browser mein chalte hain
  • Backend bhi involved hota hai (ya mocked backend)
  • Slow execution
  • Cypress/Playwright se run hote hain
  • Example: Login โ†’ Dashboard โ†’ Create Post โ†’ Logout

Analogy: Integration test = ek room check karna, E2E test = pura ghar check karna

6. fireEvent aur userEvent mein kya difference hai?
Medium โ–ผ

fireEvent:

  • Low-level DOM events dispatch karta hai
  • Ek single event fire karta hai
  • Synchronous hai
  • Example: fireEvent.click(button)

userEvent:

  • Real user interactions simulate karta hai
  • Multiple events fire karta hai (realistic behavior)
  • Asynchronous hai
  • Example: await user.click(button)
// fireEvent - single click event fireEvent.click(button); // userEvent - hover + mousedown + focus + mouseup + click const user = userEvent.setup(); await user.click(button); // userEvent.type triggers keydown, keypress, keyup for each character await user.type(input, 'Hello');

Recommendation: userEvent prefer karo kyunki ye more realistic hai.

7. Test coverage 100% hona chahiye? Explain.
Hard โ–ผ

Short Answer: Nahi, 100% coverage goal nahi hona chahiye.

Detailed Explanation:

Coverage Metrics:

  • Line Coverage: Kitni lines execute hui
  • Branch Coverage: Kitne if/else paths test hue
  • Function Coverage: Kitne functions call hue
  • Statement Coverage: Kitne statements execute hue

Problems with 100% Coverage:

  • Coverage โ‰  Quality - sab lines cover ho sakti hain but edge cases miss ho sakte hain
  • Time waste - trivial code ko test karne mein time lagta hai
  • False confidence - high coverage se lagta hai sab theek hai
  • Maintenance overhead - unnecessary tests maintain karne padte hain

Better Approach:

  • Critical business logic: 90-100% coverage
  • UI components: 70-80% coverage
  • Utility functions: 80-90% coverage
  • Focus on meaningful tests, not numbers
  • Test behavior and edge cases, not just lines

Quote: "Code coverage is a useful metric, but it's not a goal. The goal is confidence in your code."

๐ŸŽ“ Summary

Key Takeaways:

  • โœ… Testing Pyramid follow karo - zyada unit tests, kam E2E tests
  • โœ… Jest framework hai, RTL React components test karne ke liye
  • โœ… User behavior test karo, implementation details nahi
  • โœ… API calls ko mock karo for fast and reliable tests
  • โœ… Snapshots useful hain but blindly update mat karo
  • โœ… E2E tests critical user flows ke liye use karo
  • โœ… Coverage important hai but 100% goal nahi
  • โœ… Tests documentation aur confidence provide karte hain