API Testing Strategies: What to Test and When
An API can pass every unit test in its suite and still break every client that calls it, because unit tests check that functions do what the code says they do, not that the API does what the contract says it does. That gap is where most production API incidents actually come from: a field renamed, a status code changed from 200 to 204, a required parameter quietly made optional. Testing an API well means testing at several different levels, because each one catches a different class of failure.
Unit tests: the foundation, not the whole thing
Unit tests check individual functions and handlers in isolation, usually with dependencies mocked out. They’re fast, cheap to run, and good at catching logic errors: an off-by-one in pagination, a date calculation that breaks across a timezone boundary, an authorization check that lets the wrong role through. They’re also the layer teams over-invest in, because they’re the easiest to write and the coverage percentage looks good in a dashboard.
The problem is that unit tests can’t catch what happens when components talk to each other, and an API is nothing but components talking to each other, both internally and with the outside world.
Integration tests: does it work end to end
Integration tests hit real endpoints against a real (or realistic) database and check the full request-response cycle. This is where you catch the bugs unit tests miss: a database migration that broke a query, a serialization mismatch between what the ORM returns and what the API contract promises, an auth middleware that works for one route but was never wired up for another.
Keep a real test database for this layer rather than mocking the database itself. Mocking the database is exactly how a test suite ends up green while the actual query fails against real schema constraints.
Contract testing: catching breakage before it ships
This is the layer most teams skip until it burns them. Contract tests verify that an API’s actual responses match its documented schema (OpenAPI/Swagger spec, GraphQL schema, or a Pact contract) — not just that the endpoint returns 200, but that the response shape, field types, and required fields match what consumers were promised. Tools like Pact, Dredd, and Schemathesis exist specifically for this.
The value shows up hardest in teams with multiple consumers of one API: a mobile app, a web frontend, and a partner integration all calling the same endpoints. Without contract tests, a backend team can ship a change that passes their own test suite and breaks three clients simultaneously, because nothing checked the change against what those clients actually depend on.
Load and performance testing
Correctness tests answer “does it work.” Load tests answer “does it keep working under pressure.” Run tools like k6, Locust, or Gatling against a staging environment shaped like production, and ramp traffic up gradually while watching response time, error rate, and resource usage. The number that matters isn’t the peak throughput in the tool’s summary output — it’s the point where the graph bends, where latency starts climbing and errors start appearing. That’s the real capacity ceiling, and it’s usually lower than what the architecture doc assumed.
This matters especially for rate-limited APIs: test that the rate limiter itself behaves correctly under concurrent load, not just that it exists. A rate limiter with a race condition can let far more requests through than the documented limit the moment two requests hit at nearly the same millisecond.
Security testing
Authentication and authorization bugs in APIs are common enough to test for directly rather than hoping code review catches them. Check that endpoints reject expired or malformed tokens, that a user can’t access another user’s resources by changing an ID in the URL (a broken object-level authorization bug, and one of the most common API vulnerabilities in the OWASP API Security Top 10), and that rate limits and input validation actually reject the bad input rather than passing it through to a downstream service. Tools like OWASP ZAP can automate a chunk of this scanning, but a written checklist for auth edge cases catches things automated scanners don’t know to look for.
Putting it into a pipeline
Run unit and integration tests on every commit, since they’re fast enough to gate a merge without slowing anyone down. Run contract tests whenever the API schema changes, and treat a contract-test failure as a hard block rather than a warning. Run load tests before a release, not after a launch spike takes the API down, and run them again after any significant infrastructure change. Security tests belong in both places: automated scanning on every build, and a deeper manual pass before major releases.
No single layer covers everything, and that’s the actual lesson here. A team that only writes unit tests has fast, green, misleading coverage. A team that only load tests finds out too late that the API was returning the wrong data the whole time. The strategy that holds up in production is the one that uses each layer for what it’s actually good at.