Testing strategy
Choose test boundaries by risk, cost, and confidence while asserting behavior through user-visible contracts.

WHAT YOU WILL BE ABLE TO DO
Learning outcomes
- Explain testing strategy in plain language.
- Connect the behavior to the underlying browser or framework model.
- Implement the core pattern and reason through edge cases.
- Answer common follow-ups without relying on memorized phrases.
01Overview
The testing pyramid balances a large base of fast, isolated unit tests, a smaller layer of integration tests exercising real component collaboration, and a thin top layer of end-to-end tests covering critical user flows through the real (or near-real) stack.
02Mental model
- Query and assert on what a user can see and do — text, role, label — rather than internal state or class names, so tests survive refactors.
- Fewer, more meaningful end-to-end tests beat exhaustive E2E coverage: they're slow and flaky at scale.
- A failing test should point at a real behavior regression, not an implementation reshuffle.
03Examples
test('submits the form on click', async () => { render(<SignupForm />); await userEvent.type(screen.getByLabelText('Email'), 'a@b.com'); await userEvent.click(screen.getByRole('button', { name: 'Sign up' })); expect(await screen.findByText('Welcome')).toBeInTheDocument();});04Check understanding
Why does querying getByRole('button', { name: ... }) hold up better than a test that reads component.state.submitted?
It exercises the same path a real user takes and only cares about the observable outcome, so it keeps passing through internal refactors that don't change behavior — a state-based assertion breaks the moment the implementation changes shape.
DDConcept deep dives
Deep dive 1
Test risk at the cheapest useful boundary
Pure transformations are cheap to unit test, feature behavior often needs integrated rendering and network boundaries, and a small set of end-to-end journeys verifies deployment and real-system wiring. The right portfolio depends on failure impact and change frequency rather than a universal percentage pyramid.
- A test should fail for a meaningful broken contract.
- Duplicating the same assertion at every layer adds cost without equal confidence.
- Critical payment, authentication, and data-loss paths justify stronger cross-boundary coverage.
Deep dive 2
Observe the interface users receive
Query controls by role, accessible name, label, and visible text, then interact through pointer or keyboard abstractions. Assert the resulting UI, navigation, or request behavior. Tests coupled to class names, private state, or internal callback calls often survive real defects and fail harmless refactors.
- Accessible queries improve both test resilience and semantic quality.
- Avoid testing framework implementation details such as component instances.
- A request-level mock preserves more integration than mocking the data hook itself.
Deep dive 3
Remove nondeterminism instead of waiting longer
Fixed sleeps hide rather than solve races. Control clocks, random input, network responses, and shared state; then wait for an observable condition caused by the action. A flaky test is evidence of an uncontrolled dependency or ambiguous assertion and should be diagnosed like a production concurrency bug.
- Use fake timers only when the test deliberately owns time behavior.
- Give every test isolated data and cleanup.
- Retries may collect evidence in CI but should not become the permanent fix.
QAInterview questions and model answers
Attempt each answer aloud before opening it. The model answer shows the depth and precision expected in an interview; it is not a script to memorize.
Intermediate · Conceptual · 1 min · Question 1How do unit, integration, and end-to-end tests differ?Open model answer
Model answer
Unit tests isolate small logic, integration tests verify collaborating modules or rendered features, and end-to-end tests exercise deployed user journeys through real boundaries. Use the cheapest level that gives confidence in the risk, not a fixed percentage pyramid.
Open question page →Intermediate · Conceptual · 1 min · Question 2What should frontend component tests assert?Open model answer
Model answer
Assert behavior users can observe—roles, names, text, state, navigation, and side effects—rather than private component instances or implementation calls. This makes tests resilient to refactoring while still catching broken contracts.
Open question page →Intermediate · Conceptual · 1 min · Question 3When should a network request be mocked?Open model answer
Model answer
Mock at a realistic boundary when testing deterministic UI states and rare failures, preferably with request-level tools rather than mocking internal fetch wrappers. Keep some end-to-end coverage against a real compatible backend to detect contract drift.
Open question page →Intermediate · Conceptual · 1 min · Question 4What makes a test flaky?Open model answer
Model answer
Uncontrolled time, randomness, network, shared state, animation, race conditions, and ambiguous selectors commonly create nondeterminism. Fix the source and synchronize on observable state rather than adding arbitrary sleeps or retries.
Open question page →Intermediate · Conceptual · 1 min · Question 5How should accessibility be tested?Open model answer
Model answer
Query by accessible role and name, run automated rules, test keyboard paths and focus management, and manually exercise critical journeys with assistive technology. Automated rules catch only a subset of barriers.
Open question page →Intermediate · Conceptual · 1 min · Question 6What is contract testing useful for?Open model answer
Model answer
It verifies that producer and consumer assumptions about request and response shapes remain compatible without requiring every system in one end-to-end environment. Runtime schema validation still protects the application from malformed external data.
Open question page →Advanced · Conceptual · 1 min · Question 7What is the test trophy model?Open model answer
Model answer
It emphasizes integration tests as a strong confidence-to-cost layer, supported by static analysis and unit tests with a smaller end-to-end layer. It is guidance, not a quota.
Open question page →Advanced · Conceptual · 1 min · Question 8When is snapshot testing useful?Open model answer
Model answer
Small, reviewed snapshots can protect stable serialized output, but large UI snapshots obscure intent and are often updated without understanding behavioral changes.
Open question page →Advanced · Conceptual · 1 min · Question 9What should an end-to-end test seed?Open model answer
Model answer
It should create isolated deterministic data through supported APIs or fixtures, authenticate predictably, and clean ownership without depending on prior test order.
Open question page →Advanced · Conceptual · 1 min · Question 10How do contract tests reduce frontend failures?Open model answer
Model answer
They verify that client assumptions and provider schemas remain compatible before deployment, catching drift earlier while retaining focused UI tests for presentation behavior.
Open question page →SCScenario questions
Scenario 1
A test clicks '.submit-button', waits 2 seconds, and checks an internal state variable. Improve it.
- Select the button by its accessible role and name.
- Interact as a user would.
- Wait for a meaningful visible result or request completion.
- Remove the fixed sleep and internal-state assertion.
- Control the network response explicitly.
Reveal worked answer
The improved test finds the Submit button by role, fills labelled inputs, clicks it, and awaits the success message or validation error users receive. A request-level mock supplies a deterministic response. The test remains valid if internal state management or CSS classes change.