Need the #1 custom application developer in Brisbane?Click here →

Testing Front-End Code

10 min read

Front-end testing is notoriously difficult. UI changes frequently, interactions are complex, and visual correctness is hard to automate. Yet testing is essential—untested front-end code has bugs that reach production, and regression bugs appear when you change unrelated code.

The testing strategy depends on your needs and constraints. Unit tests are fast and cheap. Integration tests are realistic. E2E tests are slow and brittle but cover the full user journey. Visual tests catch unintended visual changes. The right mix depends on your risk tolerance and resources.

Unit Testing Components: React Testing Library

Unit tests verify that a component works correctly. React Testing Library is the modern standard—it encourages testing from the user's perspective, not the implementation.

Rather than testing that a button has a certain CSS class (implementation detail), test that clicking it triggers the expected behaviour (renders, calls a callback, navigates). This is more robust—the component can be refactored without breaking tests.

Example test:

test('button click triggers callback', () => {
  const handleClick = jest.fn();
  render(<Button onClick={handleClick}>Click</Button>);
  fireEvent.click(screen.getByRole('button'));
  expect(handleClick).toHaveBeenCalled();
})

Unit tests are fast (milliseconds), easy to write, and excellent for business logic. They're the foundation of the testing pyramid.

Common mistake: testing implementation details. Avoid testing the exact state shape, internal props, or internal function calls. Test user-visible behaviour.

What to Test vs What Not to Test

Test:

  • Component renders correct content
  • User interactions trigger correct behaviour
  • Form validation works
  • Error states display
  • Conditional rendering works (if permission is false, content doesn't show)

Don't test:

  • That a button has a specific CSS class (implementation detail)
  • Exact styling (that's what visual testing is for)
  • That a library was called (external library tests are the library's job)
  • Internal component state (users don't see internal state, they see rendered output)

Visual Regression Testing

Visual regression tests take screenshots and compare them to baseline screenshots. If the screenshot changed, the test fails. This catches unintended visual changes.

Tools: Chromatic (built for Storybook), Percy, BackstopJS, or Playwright's screenshot testing.

Visual testing is powerful for catching "we changed Button styling and accidentally broke ToggleButton" bugs. However, it's not perfect—screenshots can change due to font rendering, anti-aliasing, or timing. Review diffs carefully.

Visual tests are slow (several seconds per test) and shouldn't be your primary test type. But for critical visual areas, they're valuable.

End-to-End Testing: The Full User Journey

E2E tests simulate a real user using the application in a real browser. Open the app, fill a form, submit, verify the result. They test the entire stack: front-end, back-end, database.

Tools: Playwright, Cypress. Both are excellent. Playwright is faster and more reliable, Cypress has better developer experience.

Example E2E test with Playwright:

test('user can sign up', async ({ page }) => {
  await page.goto('/signup');
  await page.fill('input[name=email]', 'user@example.com');
  await page.fill('input[name=password]', 'password123');
  await page.click('button:has-text("Sign Up")');
  await expect(page).toHaveURL('/dashboard');
})

E2E tests are slow (seconds to minutes per test) and can be brittle (fail if selectors change). Don't over-test with E2E. Focus on critical user journeys: sign up, login, purchase, primary feature.

Warning
E2E tests are not a replacement for unit tests. You can't E2E test every edge case, error path, or variant. Use the testing pyramid: many unit tests, some integration tests, few E2E tests.

The Testing Pyramid

The testing pyramid illustrates the ideal test mix:

  • Unit tests (base): Many, fast, focus on components and logic
  • Integration tests (middle): Moderate number, test components working together
  • E2E tests (top): Few, slow, test critical user journeys

An inverted pyramid (many E2E tests, few unit tests) is slow and brittle. Tests fail for integration reasons, and debugging is complex.

Storybook: Developing Components in Isolation

Storybook is a tool for developing, testing, and documenting components in isolation. You write stories (examples) of components and their different states. Storybook renders them in a browser without needing the full application.

Benefits:

  • Develop components faster (instant reload, no full app build)
  • Test all component states and edge cases easily
  • Generate a component library (living documentation)
  • Integrate with visual testing tools
  • Design and development collaboration (designers can see components)

Storybook is essential for component libraries and design systems. For application-specific components, it's less critical but still valuable.

Test Coverage: The Metric

Test coverage measures how much of your code is executed by tests. 80% coverage means 80% of lines, branches, and functions are tested. Higher coverage is generally better, but more is not always useful.

Focus on coverage for critical code: business logic, error handling, conditionals. Don't obsess over covering trivial code (simple getters, thin rendering functions).

100% coverage is possible but not worth the cost. Some code is tested by integration and E2E tests, and that's fine. The goal is good coverage of code that matters, not perfect coverage of all code.

The Realistic Approach for Most Projects

For most applications:

  • Unit tests for business logic and complex components. Test utilities, hooks, form validation. Target 70-80% coverage on the most critical code.
  • Integration tests for features. Test a form submission end-to-end with a fake backend. Test that components work together.
  • E2E tests for critical user journeys. Sign up, login, payment, primary feature. Maybe 5-10 E2E tests.
  • Visual tests for design system components. If you have many shared components, visual regression tests catch unintended changes.
  • Manual QA for the rest. Designers and QA people catch issues that automated tests miss.

This balance provides good coverage without excessive testing overhead. You catch critical bugs, prevent regressions on important code, and don't waste time testing trivial code exhaustively.

Tip
Good tests enable confident refactoring. When you have good unit test coverage, refactoring existing code is safe—tests catch regressions. Without tests, refactoring is scary.

The Reality of Front-End Testing

Front-end testing requires discipline but dramatically improves code quality and developer confidence. Tests are an investment upfront that pays back immediately when they catch bugs before production, and over time when they enable safe refactoring.

Start with unit tests (fast, valuable, low complexity). Add integration tests as complexity grows. Use E2E tests for critical paths. Avoid the trap of testing implementation details or over-testing trivial code.

Testing is part of good engineering practice. Shipping untested code is gambling—tests are your safety net.