Over the last few years, one thing has become obvious to me: writing API tests isn't the difficult part.
Keeping them useful is.
Across different projects, I kept seeing the same three problems appear repeatedly. None of them were caused by bad tools or poor developers. They were simply the result of APIs evolving faster than the tests around them.
Here are the three changes that made the biggest difference for us.
1. Stop Treating Your API Specification as Documentation
For a long time we maintained three different versions of the same API:
- OpenAPI documentation
- Test cases
- Mock responses
Eventually they drifted apart.
Someone would update the API but forget to update the tests.
Or the documentation.
Or the mocks.
Instead, we started treating the OpenAPI specification as the source of truth.
The API changes once.
Everything else follows.
Even if you aren't generating tests automatically, having one canonical contract dramatically reduces maintenance.
2. Separate Contract Tests from Business Tests
One mistake we made early was putting every assertion into the same test.
For example:
Create Customer
↓
Status = 201
↓
Schema Valid
↓
Business Rules
↓
Database Validation
When the test failed, finding the actual cause took time.
Instead we now split responsibilities.
Contract tests
- Status codes
- Headers
- Required fields
- Response schema
Business tests
- Pricing
- Permissions
- Validation rules
- Workflows
The tests became much easier to understand and much easier to maintain.
3. Don't Mock Everything
For a while we mocked every external dependency.
The test suite was fast.
It was also overconfident.
Eventually we discovered that several production failures were caused by assumptions our mocks never exercised.
Today we use three layers.
- **Unit tests - **Mock everything.
- **Integration tests - **Mock only systems we don't control.
- **Pre-release validation - **Run a small suite against real services or official sandbox environments.
It's slower, but it catches issues that perfect mocks never will.
One Lesson I Didn't Expect
Authentication tests now consume more CI time than almost anything else.
OAuth flows, token refresh, service accounts, rotating secrets... they're all necessary, but they're also expensive to test properly.
I'm curious how other teams are handling this today.
Are you:
- Testing against a real identity provider?
- Running a local mock?
- Using pre-generated tokens?
- Doing something completely different?
I'd also be interested to hear how you're approaching cursor-based pagination and testing third-party APIs e.g. Stripe or Twilio, etc.
Every team seems to have a different answer, and I suspect there isn't a single "best" solution.
