Mock external APIs in tests with respx and Playwright route()
Build a test suite for a service that calls two external APIs. Using respx (for Python/httpx tests) and Playwright's page.route() (for browser tests), mock both APIs so tests run without network access, cover error and timeout scenarios impossible to trigger against real APIs, and measure the speed improvement.
Why this matters
Tests that call real external APIs are slow, flaky, and coupled to rate limits you do not control. Mocking at the HTTP boundary; not at the function boundary; gives you isolation without losing realism: your code actually makes the HTTP call, the mock just intercepts it. This distinction matters because most real integration bugs are in the request construction or response parsing, not the business logic.
Before you start
- Python with httpx and respx installed (pip install httpx respx pytest)
- Node.js with @playwright/test for the browser tests
- A Python service or script that makes HTTP calls with httpx
- Basic pytest experience
Step-by-step guide
- 1
Write a test that calls a real API and measure it
Write a pytest test that calls a real external API. Record the time taken. Run it 10 times and note the variance. This is your baseline: slow, variable, and broken when the API is down.
- 2
Mock with respx
Use the @respx.mock decorator (or respx.mock context manager). Define the mock route: respx.get('https://api.example.com/endpoint').mock(return_value=httpx.Response(200, json={...})). Run the test; it should pass without any network call and complete in under 10ms.
- 3
Test error scenarios
Define mocks that return 429 (rate limit), 500 (server error), and a network timeout (side_effect=httpx.ConnectTimeout). Write tests that verify your code handles each gracefully. These scenarios are impossible to trigger reliably against a real API.
- 4
Mock in Playwright with page.route()
In a Playwright test, use await page.route('**/api/external/**', route => route.fulfill({ status: 200, body: JSON.stringify({...}) })). Load the page and verify the UI renders correctly. Then mock a 500 and verify the error state renders correctly.
- 5
Test the timeout state in the browser
Use route => route.fulfill({ delay: 5000, status: 200 }) to simulate a slow API. Verify the UI shows a loading state and eventually a timeout error. This test is only possible with network interception; you cannot reliably slow down a real API.
- 6
Compare test execution time
Run your full suite with real network calls enabled. Then run with mocking. For a suite of 20 tests hitting 2 external APIs, the difference is typically 30-120 seconds vs under 5 seconds. That is the CI cost of untested external dependencies.