DEV Community

TestDino
TestDino

Posted on

Mistake 9/14: Stop Relying on Staging Data for Edge Case Testing

You finish the happy path tests. Coverage looks solid. Then a user reports the error screen is broken. You never caught it because your suite never actually triggered a server error.

Most teams skip edge cases entirely , or spend hours crafting corrupted data in staging just to trigger one scenario. Then the data gets cleaned up, and the test breaks again.

This is the exact pattern that makes test suites unreliable as they grow.

Playwright's page.route() removes the backend dependency completely. You return whatever response you want : empty arrays, server errors, malformed JSON, slow responses; directly from the test. Your staging environment stays untouched.


The Clean Pattern

Instead of relying on staging state, intercept the network call and return exactly what you need to test:

// Test 1: Empty state
test('shows empty state when no users found', async ({ page }) => {
  await page.route('/api/users', route =>
    route.fulfill({
      status: 200,
      contentType: 'application/json',
      body: JSON.stringify([]),
    })
  );
  await page.goto('/users');
  await expect(page.getByText('No users found')).toBeVisible();
});
Enter fullscreen mode Exit fullscreen mode

Then handle the failure and timeout cases the same way:

// Test 2: Server error
test('shows error message on server failure', async ({ page }) => {
  await page.route('/api/users', route =>
    route.fulfill({
      status: 500,
      body: JSON.stringify({ error: 'Internal Server Error' }),
    })
  );
  await page.goto('/users');
  await expect(page.getByText('Something went wrong')).toBeVisible();
  await expect(page.getByRole('button', { name: 'Retry' })).toBeVisible();
});

// Test 3: Slow response
test('shows loading spinner and timeout message', async ({ page }) => {
  await page.route('/api/users', async route => {
    await new Promise(resolve => setTimeout(resolve, 10000));
    await route.fulfill({ status: 200, body: '[]' });
  });
  await page.goto('/users');
  await expect(page.getByTestId('loading-spinner')).toBeVisible();
  await expect(page.getByText('Request timed out')).toBeVisible({ timeout: 15000 });
});

Enter fullscreen mode Exit fullscreen mode
  • Zero backend involvement: No staging data setup or cleanup needed
  • Fully deterministic: The test controls exactly what the API returns
  • Three edge cases covered: Empty state, server error, and timeout , all in one go

Empty states, error screens, and timeout messages are exactly what users see when things go wrong. Those paths deserve real test coverage, not a "we'll check it manually" comment in the PR.

Do you enforce API mocking for edge cases on your team, or is it still a free-for-all?

Drop it in the comments.

Top comments (0)