Skip to main content
Saved
Guide
Category strategy
Difficulty Intermediate

Testing Confidence Ladder

Strategy for stacking unit, integration, visual, and accessibility tests to cover modern frontends.

Den Odell
By Den Odell Added
In this article
  1. The Testing Layers
  2. Create Maintainable Test Data
  3. Write Effective Component Tests
  4. Test Complete User Journeys
  5. Protect Visual Design and Accessibility
  6. Maintain Your Test Suite

Testing everything at the UI level is slow and brittle, but testing nothing is risky. A suite built in layers works better: fast tests catch logic bugs, and slower integration and visual tests cover what those can’t. This guide covers how to build a test suite you can trust without it taking too long to run.

The Testing Layers

Each layer catches different bugs at different speeds, and the pyramid principle still applies: lots of fast tests at the bottom, fewer slow tests at the top:

  • Component tests: Fast, focused tests for individual UI components with Component Testing.
  • Logic tests: Even faster tests for reducers and hooks with Stateful Logic Testing, which need no DOM and no rendering because they exercise pure logic.
  • Integration tests: Slower tests that verify multiple components work together with Integration Testing, covering routing and data fetching.
  • Visual tests: Screenshot comparisons with Visual Regression catch UI changes you didn’t intend to make.
  • Accessibility tests: Automated checks and manual screen reader testing with Accessibility Testing.

Create Maintainable Test Data

If you copy large JSON fixtures into your tests, the maintenance burden puts your team off writing tests.

  • Use test builders: You can create Test Data Builder functions that generate valid test data with defaults. Getting a user becomes as simple as calling buildUser(), or buildUser({ role: 'admin' }) when you need a specific role.
  • Keep builders close: Builders are easiest to maintain next to the code they support. When data models change, tests break immediately and obviously, which makes the necessary updates clear.
  • Provide smart defaults: Builders should work with zero configuration but allow any property to be overridden. This prevents common issues like undefined property errors while keeping tests concise.

Write Effective Component Tests

Component tests verify UI behavior without launching a browser, and they should stay fast and focused.

  • Mock minimally: It’s best to mock only what you must, letting real context providers and hooks run and keeping other dependencies real when possible. Over-mocking creates tests that pass while the real application breaks.
  • Test user-facing behavior: Good component tests verify what users see and do, such as whether clicking a button triggers the correct action or whether form validation works. Testing implementation details like state variable names is best avoided, since those break during refactoring.
  • Keep tests fast: Component test suites should run in seconds. If they take minutes, it’s worth finding out why, because developers run fast tests often and skip slow ones.

Test Complete User Journeys

Integration tests verify that components work together. They’re slower but catch issues that component tests miss.

  • Test critical paths: Integration Testing suits essential flows such as login and checkout. You can mock API calls with MSW but still test real routing and real state management.
  • Be selective: One integration test per critical user journey is usually enough. More than that tends to make suites slow and flaky. Tests that fail randomly teach developers to ignore failures, which undermines the entire test suite.
  • Verify analytics: It’s worth testing that tracking events fire correctly. With broken analytics, you’re making decisions without data, and the problem often goes unnoticed until someone asks about missing metrics.

Protect Visual Design and Accessibility

Component and integration tests check that the code behaves correctly. Visual and accessibility tests check what people see and whether everyone can use it.

  • Snapshot key screens: You can run Visual Regression tests on design system components and critical pages, covering light and dark themes at different screen sizes to catch accidental CSS changes early.
  • Test accessibility automatically: With Accessibility Testing in place, you can verify ARIA attributes and color contrast. Automated tools catch common issues, and manual screen reader testing catches the problems that need a person to notice them, so you need both.

Maintain Your Test Suite

A test suite gets worse without maintenance, so it deserves the same care as production code.

  • Track layered coverage: Overall coverage percentages tell you little. It’s more useful to track which critical flows have component tests, integration tests, visual tests, and accessibility tests. A critical flow that’s missing one of those layers is the first thing to fix.
  • Fix flaky tests immediately: A test that sometimes passes and sometimes fails is worse than no test because it trains developers to ignore failures. You should fix flaky tests the same day they appear, or delete them.
  • Match tests to workflow: Fast component and logic tests can run on every commit, and slower integration and visual tests on pull requests. Full accessibility audits can wait until just before a release.

Remember: The best test suite is one your team actually runs. Teams keep running tests that are fast and reliable, and they disable the ones that are slow and flaky.

Newsletter

A monthly email
from Den Odell.

Behind-the-scenes thinking on frontend patterns, site updates, and more.

No spam. Unsubscribe anytime.