Softechinfra
Development

Frontend Testing Guide: Unit, Integration & E2E Strategies

Master frontend testing with React Testing Library, Jest, Cypress, and Playwright. Complete guide with code examples.

Hrishikesh BaidyaHrishikesh Baidya
November 22, 202211 min read
Frontend Testing Guide: Unit, Integration & E2E Strategies

Frontend testing is essential for maintaining quality as applications grow. Our development team at Softechinfra shares strategies for comprehensive testing.

70%
Unit Tests
20%
Integration Tests
10%
E2E Tests
80%+
Target Coverage

Testing Pyramid

💡 The Testing Pyramid

Balance your test suite: Unit tests (70%) for speed, Integration tests (20%) for component interactions, E2E tests (10%) for critical user journeys.

Unit Testing

Testing Library Approach

typescript
import { render, screen } from '@testing-library/react';
import { Button } from './Button';

describe('Button', () => {
  it('renders children correctly', () => {
    render(<Button>Click me</Button>);
    expect(screen.getByText('Click me')).toBeInTheDocument();
  });

  it('calls onClick when clicked', () => {
    const handleClick = jest.fn();
    render(<Button onClick={handleClick}>Click</Button>);

    screen.getByRole('button').click();
    expect(handleClick).toHaveBeenCalledTimes(1);
  });

  it('is disabled when disabled prop is true', () => {
    render(<Button disabled>Click</Button>);
    expect(screen.getByRole('button')).toBeDisabled();
  });
});

Best Practices

  • Test behavior, not implementation
  • Use accessible queries
  • Keep tests focused
  • Avoid testing styling
  • What to Unit Test

  • Pure functions
  • Utility functions
  • Custom hooks
  • Component rendering
  • User interactions
  • Integration Testing

    Component Integration

    typescript
    import { render, screen, waitFor } from '@testing-library/react';
    import userEvent from '@testing-library/user-event';
    import { SearchForm } from './SearchForm';
    import { SearchResults } from './SearchResults';
    import { SearchPage } from './SearchPage';
    
    describe('SearchPage', () => {
      it('shows results when search is submitted', async () => {
        render(<SearchPage />);
    
        await userEvent.type(
          screen.getByRole('textbox'),
          'test query'
        );
        await userEvent.click(screen.getByRole('button', { name: /search/i }));
    
        await waitFor(() => {
          expect(screen.getByText(/results for/i)).toBeInTheDocument();
        });
      });
    });

    API Integration

    typescript
    import { rest } from 'msw';
    import { setupServer } from 'msw/node';
    import { render, screen, waitFor } from '@testing-library/react';
    import { UserProfile } from './UserProfile';
    
    const server = setupServer(
      rest.get('/api/user', (req, res, ctx) => {
        return res(ctx.json({ name: 'John Doe', email: 'john@example.com' }));
      })
    );
    
    beforeAll(() => server.listen());
    afterEach(() => server.resetHandlers());
    afterAll(() => server.close());
    
    describe('UserProfile', () => {
      it('displays user data from API', async () => {
        render(<UserProfile userId="123" />);
    
        await waitFor(() => {
          expect(screen.getByText('John Doe')).toBeInTheDocument();
        });
      });
    });

    E2E Testing

    Playwright Example

    typescript
    import { test, expect } from '@playwright/test';
    
    test.describe('Checkout Flow', () => {
      test('completes purchase successfully', async ({ page }) => {
        await page.goto('/products');
    
        // Add to cart
        await page.click('[data-testid="product-1"]');
        await page.click('button:has-text("Add to Cart")');
    
        // Go to checkout
        await page.click('[data-testid="cart-icon"]');
        await page.click('button:has-text("Checkout")');
    
        // Fill form
        await page.fill('#email', 'test@example.com');
        await page.fill('#card-number', '4242424242424242');
        await page.fill('#card-expiry', '12/25');
        await page.fill('#card-cvc', '123');
    
        // Submit
        await page.click('button:has-text("Pay")');
    
        // Verify
        await expect(page.locator('h1')).toHaveText('Thank you!');
      });
    });

    Cypress Alternative

    typescript
    describe('Login Flow', () => {
      it('logs in successfully', () => {
        cy.visit('/login');
        cy.get('#email').type('user@example.com');
        cy.get('#password').type('password123');
        cy.get('button[type="submit"]').click();
        cy.url().should('include', '/dashboard');
      });
    });

    Testing Hooks

    Custom Hook Testing

    typescript
    import { renderHook, act } from '@testing-library/react';
    import { useCounter } from './useCounter';
    
    describe('useCounter', () => {
      it('increments counter', () => {
        const { result } = renderHook(() => useCounter());
    
        act(() => {
          result.current.increment();
        });
    
        expect(result.current.count).toBe(1);
      });
    });

    Visual Testing

    Snapshot Testing

    typescript
    import { render } from '@testing-library/react';
    import { Card } from './Card';
    
    it('matches snapshot', () => {
      const { container } = render(
        <Card title="Test" description="Description" />
      );
      expect(container).toMatchSnapshot();
    });

    Visual Regression

    Tools: Chromatic, Percy, Playwright

    Test Organization

    File Structure

    code
    src/
      components/
        Button/
          Button.tsx
          Button.test.tsx
          Button.stories.tsx
      utils/
        format.ts
        format.test.ts
    e2e/
      checkout.spec.ts
      login.spec.ts

    CI/CD Integration

    yaml
    # GitHub Actions
    name: Tests
    on: [push, pull_request]
    
    jobs:
      test:
        runs-on: ubuntu-latest
        steps:
          - uses: actions/checkout@v3
          - uses: actions/setup-node@v3
          - run: npm ci
          - run: npm run test:unit
          - run: npm run test:integration
          - run: npm run test:e2e

    Conclusion

    "A balanced testing strategy catches bugs early and maintains confidence. Start with unit tests, add integration tests for critical flows, and use E2E for key user journeys."
    — Hrishikesh Baidya, Lead Developer

    Comprehensive testing is essential for maintaining quality at scale. We implement testing strategies for projects like TalkDrill and ExamReady. See our TypeScript best practices for more development insights.

    Need Testing Implementation Help?

    Our team implements comprehensive testing strategies for React and modern web applications with Jest, Testing Library, and Playwright.

    Improve Your Testing
    Tags:
    TestingFrontendReactJavaScriptQuality Assurance
    Share this post:
    Hrishikesh Baidya

    Hrishikesh Baidya

    CTO at Softechinfra specializing in Python, system architecture, and building secure, scalable software solutions.