Frontend Testing Strategy (Unit, Integration, E2E, MSW, a11y)
A resilient frontend testing strategy balances fast unit tests (Vitest/Jest) for pure utility logic, component integration tests (React Testing Library + MSW) testing user behavior from the user's perspective, automated accessibility checks (axe-core), and focused end-to-end smoke flows (Playwright).
Why it matters in interviews
Visual & Interactive Explanation
1. Static Analysis
TypeScript strict mode + ESLint (catches syntax, typos, type mismatches during development)
2. Unit Tests
Vitest / Jest: pure functions, utility algorithms, custom hooks with isolated edge cases
3. Component Integration (Largest ROI)
React Testing Library + MSW: render full component trees, simulate user clicks, test API flows
4. Accessibility Tests
axe-core automated checks for missing labels, contrast, and focus trapping in CI
5. End-to-End Tests
Playwright: real browser testing of critical business flows (Auth, Checkout, Payment)
Code Examples & Implementation
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { http, HttpResponse } from 'msw';
import { setupServer } from 'msw/node';
import { LoginForm } from './LoginForm';
// 1. Mock network request using MSW (network level, not mocking fetch directly)
const server = setupServer(
http.post('/api/login', async ({ request }) => {
const { email } = await request.json() as any;
if (email === 'admin@test.com') {
return HttpResponse.json({ user: { name: 'Admin User' } });
}
return new HttpResponse(null, { status: 401 });
})
);
beforeAll(() => server.listen());
afterEach(() => server.resetHandlers());
afterAll(() => server.close());
test('submits login form and displays welcome message on success', async () => {
const user = userEvent.setup();
render(<LoginForm />);
// 2. Query by accessible role (matches how screen readers and users see UI)
const emailInput = screen.getByRole('textbox', { name: /email/i });
const passwordInput = screen.getByLabelText(/password/i);
const submitButton = screen.getByRole('button', { name: /sign in/i });
// 3. Simulate user interactions
await user.type(emailInput, 'admin@test.com');
await user.type(passwordInput, 'Secret123!');
await user.click(submitButton);
// 4. Assert user-visible outcome
expect(await screen.findByText(/welcome back, admin user/i)).toBeInTheDocument();
});Interview-Ready Answers
Follow Kent C. Dodds' Testing Trophy: prioritize Component Integration tests using React Testing Library and MSW, supported by fast unit tests for pure algorithms, automated axe-core accessibility checks, and critical-path Playwright E2E tests.
Official Documentation & Specifications
Follow-up Questions & Deep Dives
Common Mistakes & Anti-Patterns
- Testing component internal state (e.g. wrapper.state('count')) instead of asserting on DOM text and accessibility attributes.
- Using fireEvent instead of userEvent (userEvent faithfully dispatches all accompanying mouse, keyboard, and focus events).
- Writing hundreds of slow, flaky E2E tests for minor UI variants instead of covering them with fast Integration tests.
Real-World Architectural Scenario
A team's test suite contains 4,000 Enzyme unit tests that take 35 minutes to run and fail on every minor CSS refactor without catching real bugs. How do you lead a testing overhaul?
Rate Your Readiness
Rate how comfortably you can explain this in an interview to update your global readiness gauge: