Setting up mock APIs for frontend component tests

When component tests intermittently fail or produce non-deterministic snapshots, uncontrolled network traffic is the primary culprit. Engineers must immediately audit the test runner console for pending fetch calls, timeout warnings, and unhandled promise rejections before attempting architectural refactors. Isolate network-dependent failures using browser DevTools Network waterfall profiling, parse runner logs for async stack traces, and execute timing analysis scripts to quantify latency variance.

# Quick diagnostic: Extract unhandled promise rejections and pending fetches from test output
npm test -- --verbose 2>&1 | grep -E "(Unhandled|pending|fetch|timeout)"

Diagnosing Network Dependency Failures

Direct HTTP calls during unit or integration tests violate core Component Testing Fundamentals by coupling UI logic to unpredictable backend behavior. The root cause typically stems from missing request interception at the transport layer, allowing fetch or axios calls to escape the test sandbox. This leakage introduces race conditions, environment-specific CORS blocks, and data shape inconsistencies that corrupt visual regression comparisons.

To trace the exact point of failure, attach network interceptor logging middleware during test initialization:

// network-trace.ts: Capture unmocked requests before they hit the network
import { setupWorker } from 'msw';

const worker = setupWorker();
worker.events.on('request:start', ({ request }) => {
  console.warn(`[UNMOCKED] ${request.method} ${request.url}`);
});

// Mount component lifecycle tracing
worker.events.on('response:mocked', ({ request, response }) => {
  console.debug(`[INTERCEPTED] ${request.url} -> ${response.status}`);
});

Implementing Deterministic Request Interception

To resolve network leakage, intercept HTTP requests at the lowest possible layer before component initialization. By defining strict Mock Boundaries during test setup, teams guarantee that every outbound call returns a predefined, schema-validated response. This eliminates environment variability and ensures component state transitions remain fully deterministic across local and headless runners.

Enforce strict JSON schema validation to prevent silent data drift:

// schema.ts: Production-aligned payload contracts
import { z } from 'zod';

export const UserSchema = z.object({
  id: z.string().uuid(),
  name: z.string().min(1),
  role: z.enum(['admin', 'viewer']),
});

Configure runtime interceptors and enforce lifecycle control:

// handlers.ts & test-setup.ts: MSW + Jest/Playwright integration
import { http, HttpResponse } from 'msw';
import { setupServer } from 'msw/node';
import { UserSchema } from './schema';

const handlers = [
  http.get('/api/users', () => {
    const payload = UserSchema.parse({
      id: '1',
      name: 'Test User',
      role: 'viewer',
    });
    return HttpResponse.json(payload);
  }),
];

const server = setupServer(...handlers);

beforeAll(() => server.listen({ onUnhandledRequest: 'error' }));
afterEach(() => server.resetHandlers()); // Clears runtime overrides
afterAll(() => server.close()); // Teardown clears all pending network handlers

CI Pipeline Integration and Flakiness Prevention

Stabilizing tests in continuous integration requires strict lifecycle control over mock responses. By synchronizing fake timers with interceptor delays and caching fixture definitions in pipeline artifacts, teams eliminate timing-based flakiness. Integrating deterministic mocks into visual regression workflows ensures UI snapshots only update when intentional state contracts change, preventing false-positive failures during deployment cycles.

# .github/workflows/component-tests.yml
steps:
  - uses: actions/cache@v3
    with:
      path: ./test/fixtures
      key: ${{ runner.os }}-mock-fixtures-${{ hashFiles('**/schema.ts') }}
  - run: npx playwright test --retries=0
    env:
      CI: true
      MOCK_ROUTING: strict

Apply controlled response delays and clock synchronization to eliminate race conditions in headless environments:

// playwright-clock.ts: Deterministic timing control
import { test } from '@playwright/test';

test('handles delayed API response without flakiness', async ({ page }) => {
  await page.clock.install();
  await page.route('/api/data', async (route) => {
    await page.clock.pauseAt(1000); // Simulate network latency
    await route.fulfill({ json: { status: 'ok' } });
  });

  await page.goto('/dashboard');
  await page.clock.runFor(1000); // Advance time deterministically
  await expect(page.locator('[data-testid="status"]')).toHaveText('ok');
});

Implementation Verification Checklist