Silas Tyokaha
← Back to blog
Testing Strategies for Frontend Teams

Testing Strategies for Frontend Teams

testingfrontend

Testing Strategies for Frontend Teams

Finding the right balance between test coverage and development velocity is crucial for frontend teams.

The Testing Pyramid


/\

/E2E\

/------\

/ Integ \

/----------\

/ Unit \

/--------------\



Unit Tests (70%)

Fast, isolated tests for individual functions and components.

Integration Tests (20%)

Test how components work together.

E2E Tests (10%)

Full user workflows from UI to backend.

Unit Testing Components

Use React Testing Library for better maintainability:


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

import { ProjectCard } from './ProjectCard';

test('renders project title and description', () => {

render(

title="My Project"

description="A cool project"

/>

);

expect(screen.getByText('My Project')).toBeInTheDocument();

expect(screen.getByText('A cool project')).toBeInTheDocument();

});



Integration Testing

Test component interactions and data flow:


test('filters projects by tag', async () => {

render();

const filterButton = screen.getByRole('button', { name: 'Next.js' });

await user.click(filterButton);

const projects = screen.getAllByRole('article');

expect(projects).toHaveLength(2); // Only Next.js projects

});



E2E Testing with Playwright

Critical user journeys only:


import { test, expect } from '@playwright/test';

test('user can submit contact form', async ({ page }) => {

await page.goto('/');

await page.click('a[href="#contact"]');

await page.fill('input[name="name"]', 'John Doe');

await page.fill('input[name="email"]', 'john@example.com');

await page.fill('textarea[name="message"]', 'Hello!');

await page.click('button[type="submit"]');

await expect(page.locator('text=Message sent')).toBeVisible();

});



Best Practices

1. Test Behavior, Not Implementation

❌ Bad: expect(component.state.counter).toBe(5)

✅ Good: expect(screen.getByText('Count: 5')).toBeInTheDocument()

2. Use Data-Testid Sparingly

Prefer semantic queries (role, label, text) over test IDs.

3. Mock External Dependencies

Mock API calls, third-party services, and heavy computations.


vi.mock('@/lib/api', () => ({

fetchProjects: vi.fn(() => Promise.resolve(mockProjects))

}));



4. Snapshot Tests with Caution

They break often and can create false confidence. Use for:

  • Error messages
  • Complex computed output
  • Generated HTML/emails
  • Continuous Integration

    Run tests on every PR:

    
    

    name: Tests

    on: [pull_request]

    jobs:

    test:

    runs-on: ubuntu-latest

    steps:

    - uses: actions/checkout@v3

    - run: npm ci

    - run: npm test

    - run: npm run test:e2e

    
    
    

    Metrics to Track

  • **Code coverage**: Aim for 80%+ but don't obsess
  • **Test execution time**: Keep under 5 minutes
  • **Flaky test rate**: Should be < 1%
  • Conclusion

    Good testing isn't about 100% coverage—it's about confidence to ship. Focus on critical paths and maintain fast feedback loops.