"API testing" is a category, not a single kind of test. Inside that category live half a dozen distinct test types, each designed to catch a different class of bug. A functional test asks "does this endpoint behave correctly?" — a performance test asks "does it still behave correctly under load?" — a contract test asks "does it still match what consumers expect?" The risk you're worried about determines which type to write. This lesson maps the landscape so you can pick the right tool for each job.
The map
- – Right output for valid input
- – Right error for invalid input
- – Field types and shapes
- – Service A → Service B
- – Cascading effects
- – Real third parties
- – Schema match
- – Consumer expectations
- – Pact, OpenAPI
- Response time –
- Throughput –
- Behaviour at capacity –
- Auth bypass –
- Injection –
- Data leaks –
Five branches, plus a quiet sixth — smoke tests — which are really a tiny subset of functional tests run after every deploy. We'll touch all of them.
Functional tests
The bread and butter. A functional test asks: does this endpoint do what the spec says it does?
For a POST /api/users endpoint, functional tests cover:
- Valid input → 201 Created with the expected body shape.
- Invalid input (missing email) → 400 Bad Request with a helpful error.
- Wrong types (
age: "thirty") → 400 or 422. - Field-level validation (email regex, password length) → 400 with the specific field flagged.
- Response shape — are all the required fields present? Are types correct?
If you only had time to write one type of test, write functional tests. They cover 70-80% of what users actually hit.
Integration tests
When two or more services collaborate, integration tests verify they work together. The risk being tested is the seam — the boundary where one service hands off to another.
Examples:
- Order → payment. Your order service calls the payment service. Does a successful payment flow through? What about a declined card?
- User signup → email service. Creating a user triggers a welcome email. Does the email actually get sent?
- Cascading deletes. Deleting a user deletes their orders. Does it also remove their saved cards?
Integration tests usually run against real (or near-real) downstream services. That's slower than mocking, but it's the only way to catch the bugs that live at the seam — wrong header names, missing fields, format mismatches between services.
Contract tests
A contract test asks: does this API still meet what its consumers expect?
This sits between functional and integration. The provider has tests that say "I return X when called with Y." The consumer has tests that say "I expect to receive X when I call Y." A contract test verifies the two stay aligned over time.
Two flavours you'll meet:
- Schema-based. The API publishes an OpenAPI/Swagger spec. Tools like Schemathesis or Dredd auto-generate tests from the spec and verify the running API conforms.
- Consumer-driven. Consumers write tests that record expected interactions (Pact files). The provider then runs those interactions and verifies it can satisfy each.
Contract tests catch the worst class of bug in a microservices world: a provider releases a "minor" change and breaks every consumer in production. We'll cover these in detail in Chapter 7.
Performance tests
A performance test asks: does the API still behave correctly under load?
Three sub-flavours:
- Load tests — sustained realistic traffic. "Can we handle 500 requests per second for 10 minutes?"
- Stress tests — push beyond expected load until something breaks. "When does the API start failing? Does it fail gracefully or crash?"
- Spike tests — sudden bursts. "What happens when traffic 10× in a single second?"
Common metrics: average response time, p50/p95/p99 latency (the slowest 50%/5%/1% of requests), throughput, error rate. Tools like k6, Gatling, JMeter, and Locust are built for this. A few teams have CI gates on p95 latency — most don't, and probably should.
Security tests
API security testing is its own discipline. The QA-relevant slice:
- Authentication bypass. Can you reach an authenticated endpoint without a token? With an expired one? With another user's token?
- Authorisation flaws. Can a "viewer" perform actions only "admins" should be allowed?
- Injection. Does the API safely handle inputs containing SQL, scripts, or shell metacharacters?
- Data exposure. Does the response include fields it shouldn't (password hashes, internal IDs, other users' data)?
- CORS misconfiguration. Can a browser at any origin call your API?
You don't need to be a penetration tester to run these. A handful of negative auth tests in your CI suite catches the most common mistakes — covered in Chapter 3.
Smoke tests
Smoke tests are the lightest possible check that the API is alive after a deploy. Two or three calls — usually a GET /health, a login, and one core read — that prove the system is responding. Run them seconds after deploy; if any fails, roll back.
Smoke tests are not a substitute for functional tests. They catch deployment failures, not logic bugs.
A worked example: user registration
For a single endpoint — POST /api/users — what would each type look like?
| Type | Example test |
|---|---|
| Functional | Valid body returns 201 with id, email, createdAt. |
| Integration | Successful registration triggers a welcome email via the mailer service. |
| Contract | The response matches the published OpenAPI schema for POST /users. |
| Performance | 100 concurrent registrations all complete under 2s with no 5xx. |
| Security | Registering with an SQL-injection email string is rejected with 400. |
| Smoke | GET /api/health returns 200 within 1s after deploy. |
Six tests, five different test types, one endpoint. None duplicates the others — each catches a different class of bug.
Choosing what to write first
For a brand new endpoint, in priority order:
- Functional, happy path. One test that proves the endpoint works at all.
- Functional, key error paths. Missing fields, wrong types, unauthorised access.
- Smoke test. Add it to the post-deploy suite.
- Contract test. If you have an OpenAPI spec, bind to it.
- Integration test. If the endpoint touches another service, exercise the seam.
- Performance and security tests. Once the basics are solid.
Most teams stop at step 3 and bolt on the rest opportunistically. That's fine — coverage grows over time.
⚠️ Common mistakes
- Calling everything an "integration test." The word is overloaded. Be specific — "this is a functional test against a real DB" or "this is a contract test using Pact." Vague vocabulary leads to redundant or missing coverage.
- Skipping contract tests because the team is small. Even with two services, contract tests pay for themselves the first time a "harmless" rename breaks the consumer in production.
- Running performance tests against a shared environment. A 100rps load test on staging will flatten everyone else's work. Run perf tests in a dedicated environment, ideally production-like in shape but isolated in tenancy.
🎯 Practice task
Map an endpoint to the test types. 25-30 minutes.
- Pick one endpoint from your current product (or any public API — Stripe
POST /charges, GitHubPOST /repos/{owner}/{repo}/issues). - For each of the six types — functional, integration, contract, performance, security, smoke — sketch one specific test you'd write. One sentence each. Use the table format above.
- Identify which two types your team currently does least. That's your most valuable next investment.
- Pick the highest-priority test from your list and write the request shape (method, URL, headers, body) and the expected response. No code yet — just the contract.
- Stretch: open the API Testing Concepts cheat sheet and find one test type from the cheat sheet that you didn't list above. Add it to your understanding.
You now have a vocabulary for what you're testing and why. The next lesson moves from types to design: how to take an endpoint and systematically generate the test cases that cover it.