Skip to main content

Mock Loki is now Stage! Read the announcement

Back to blog
Engineering7 min read

The Case Against Mocking: Why Your Tests Are Lying to You

Mocking frameworks promise isolated, fast tests. But at what cost? We explore why gateway-level testing is a better approach for API-dependent applications.

EW
Emily Watson
VP of Engineering
December 1, 2024

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:

typescript
// 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:

  1. Your code makes real HTTP calls
  2. Stage intercepts the response
  3. You can mutate that response to test any scenario
  4. 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.

Share this article

Ready to transform your API testing?

Join thousands of developers who ship with confidence using Stage.