Mocking is everywhere. Almost every testing guide tells you to mock your dependencies. But I'm going to argue that mocking—as commonly practiced—is doing more harm than good.
The Promise of Mocking
The idea is simple: replace real dependencies with fake ones that return predetermined responses. This gives you:
- Speed: No network calls, no database queries
- Isolation: Test one thing at a time
- Control: Deterministic inputs and outputs
Sounds great, right? Here's the problem.
Mocks Drift from Reality
The moment you write a mock, it starts lying to you. Real APIs change. They add fields, deprecate endpoints, modify error formats. Your mocks don't know about any of this.
I've seen teams ship bugs because their mocks returned {"error": "not_found"} when the real API returns {"errors": [{"code": "NOT_FOUND", "message": "Resource not found"}]}.
Mocks Test the Wrong Thing
When you mock an HTTP client, you're testing that your code calls the mock correctly. You're not testing that your code handles real HTTP responses correctly.
Consider this test:
// What we're actually testing
expect(mockHttpClient.get).toHaveBeenCalledWith('/api/users/123');
// What we should be testing
expect(user.name).toBe('John Doe');
expect(user.email).toBe('john@example.com');
The first test passes even if the real API doesn't exist!
Mocks Hide Integration Bugs
The most dangerous bugs live at integration boundaries. Mocking removes those boundaries from your tests entirely.
Real-world failures:
- Timeout handling: Your mock returns instantly, but the real API takes 30 seconds
- Rate limiting: Your mock never returns 429s
- Partial failures: Your mock succeeds or fails completely, never partially
A Better Approach
Instead of mocking at the code level, test against real HTTP responses that can be staged dynamically.
This is exactly what Stage does. When you test through Stage:
- Your code makes real HTTP calls
- Stage intercepts the response
- You can mutate that response to test any scenario
- Your code handles the response as it would in production
No mocks. No drift. No lies.
When Mocking Makes Sense
To be fair, there are cases where mocking is appropriate:
- Unit testing pure functions: No dependencies to mock
- Performance testing: When network latency would skew results
- Development velocity: When the real service doesn't exist yet
But for integration tests—where most bugs live—consider testing against real (or realistically staged) HTTP responses.
Conclusion
Mocking frameworks are useful tools, but they've been overused to the point of testing theater. If you want confidence that your code works with real APIs, you need to test against real (or realistically staged) APIs.
That's what Stage is built for. Try it free and see the difference.