API Testing: A Complete Guide to Cases, Types and Tools
What API testing proves, the four types worth keeping separate, the six parts of a real case, and how a written case connects to the test that runs it.
Most teams meet API testing the hard way. A payment endpoint starts answering with a 200 status and an empty body. Nobody notices for two days, because the browser tests are all green and the page still renders. The bug arrives as a support ticket about missing receipts.
This guide is for the QA engineer or SDET who has been told to "add some API tests". You want to know what to write down, not which library to install. It covers what testing at the API layer proves and the types worth keeping separate. It also covers what a single case has to contain, and how that written case connects to the automation that runs it.
What API testing is, in plain terms
A user interface test drives the screen. It clicks a button, waits for the page to settle, and reads the answer off what got rendered. An API test skips the screen entirely. It sends a request straight to the service and checks the reply that comes back.
Think of a restaurant. A user interface test is a customer who reads the menu, orders from a waiter, waits at the table, and inspects the plate that arrives. An API test walks up to the kitchen window, hands over the order slip, and checks what gets handed back. Same kitchen, far fewer steps in between, and a much clearer view of who made the mistake.
That difference matters more than it sounds. When a browser test for checkout fails, the cause could be the button, the page script, the network call, the service, or the database. When an API test for POST /v1/orders fails, there is one participant left to blame. You have gone from a list of suspects to a name.
The other difference is timing. The screen is usually the last thing built. The endpoint often exists weeks earlier, behind a feature flag, with nothing pointing at it yet. Tests at that layer can start while the front end is still a wireframe.
Why teams move tests down to the API layer
Picture a suite of 900 browser tests that takes 50 minutes on a clean run and fails 6 times a week for reasons nobody can reproduce. Every one of those failures costs someone 20 minutes of reading logs to conclude that nothing was wrong. That is roughly two hours a week spent proving a negative.
Four things get better when the same checks move down a layer:
- Speed. Four hundred API checks finish in under three minutes because there is no browser to start, no page to paint, and no animation to wait for.
- Earlier warning. The endpoint is testable the day it merges. A bug found then costs a conversation. The same bug found in a release candidate costs a meeting.
- Stability. There are no locators to break and no waits to tune, so a passing test stays passing. Most flaky tests are a design problem rooted in timing and shared state, and removing the browser removes a large share of both.
- Precision. A failure points at one service and one endpoint, which is the difference between a fix and an investigation.
None of this makes browser tests unnecessary. It makes them a small, deliberate layer instead of the whole strategy. If you are deciding what to move first, a simple scoring pass on what to automate first beats arguing about it in a planning meeting.
The types of API testing, and what each one proves
People say "API testing" as if it were one activity. It is at least four, and mixing them is why suites become hard to read. Keep them separate and every failure tells you something specific.
- Functional testing. One endpoint, one behaviour, valid input, correct reply. Does
GET /v1/invoices/{id}return the invoice, with the right total, for a customer who owns it? - Contract testing. Not whether the answer is right, but whether its shape still matches what was agreed. Fields present, types correct, required values not null. The plug still fits the socket, whatever the appliance does with the current.
- Integration testing. Two or more services working together. An order is created, the inventory service reserves stock, the billing service raises an invoice, and all three agree on the same order id.
- Negative testing. Bad input, missing token, expired token, wrong tenant, a string where a number belongs. This is where most real defects hide, which is why negative test cases for APIs deserve their own pass rather than a few afterthoughts.
Two more get mentioned in the same breath and should not live in the same suite:
- Security checks. Confirming that a customer cannot read another customer's invoice belongs in your negative cases. A real security testing programme, with fuzzing and dependency scanning and a threat model, is its own discipline with its own tooling.
- Performance checks. Whether the endpoint answers in 200 milliseconds under 500 concurrent users is a different question, run with different tools, on different infrastructure. Do not smuggle it into a functional suite where it will fail on a slow build agent and get retried until it passes.
What a single API test case actually contains
This is the part most teams skip, and it is the part that decides whether the suite survives a year. A written case has six parts. Miss any one of them and the next person cannot run it or trust it.
- A name that states the behaviour. "Create invoice fails when the customer has no payment method" tells you what broke from the failure list alone. "Invoice test 4" tells you nothing.
- Endpoint and method.
POST /v1/invoices, written out, including the version. Not "the invoice API". - Preconditions. What has to be true before the request is sent. A customer exists, that customer has no saved payment method, the tenant is on the standard plan. This is where most cases quietly fail, because the state they assume was created by a different test that no longer runs.
- The request. Headers, path and query parameters, and the body. Include the auth header as a role, not a pasted token: "authenticated as a workspace admin" survives a credential rotation.
- The expected status. One number.
422, not "an error". - The expected response body. The specific fields that must be there and what they must contain. For the case above: an
errorsarray with one entry whosecodeispayment_method_missing.
Put together, that is a case a new joiner can execute by hand on a Tuesday and an automated test can assert against on every commit. The longer form of this, with a full worked example, is in what a real API test case contains.
The most common defect in an API case
It is not a missing endpoint or a wrong method. It is an expected result that reads "request succeeds" or "returns correct data".
Both are unfalsifiable. A response of 200 OK with an empty array satisfies "request succeeds". So does the payment endpoint from the opening paragraph, the one that returned 200 with no body for two days. The check passed. The customer got no receipt.
The fix takes ten seconds per case. Name the status code. Then name the two or three fields whose values actually prove the behaviour happened. Not every field in the payload, which makes the case brittle for no gain. Just the ones that would be wrong if the feature were broken.
How a written case connects to the automation that runs it
A documented case and an automated test are two views of one thing. The document says what must be true. The code proves it on every commit. They stay in sync through one shared identifier, and nothing else.
In practice that means three small habits:
- Give every case a stable id and put that id in the automated test, in the test name or an annotation. When the CI job reports a failure, the id tells you which documented behaviour just stopped being true.
- Send results back to the record, not just to the CI log. A log is deleted in 30 days. A run history tells you that this case has failed on Fridays since the caching change in June.
- Keep the requirement link at the top. Requirement, case, run. That chain answers "how do we know this works" without reopening a ticket from four sprints ago. It is also the backbone of a usable audit trail.
Tesbo is the record and the drafting help, not the runner. It holds the documented cases, the review state and the run history, and it can draft cases from a specification for a human to approve. The framework that fires the request and the CI job that schedules it stay yours. That split is deliberate, and it is the same reasoning behind treating test case management as the record a release rests on.
API testing tools, grouped by the job they do
Tool lists go stale in a year. The categories do not. Work out which job you are hiring for, then pick whatever your team can already read.
- Request clients. Postman, Insomnia, Bruno, plain
curl. Good for exploring an endpoint by hand and for the first version of a case. Not a place to keep 400 assertions. - Code libraries. RestAssured for Java,
requestswith pytest for Python, supertest or the Playwright request fixture for Node. This is where a real suite lives, because it gets reviewed, versioned and refactored like the rest of your code. - Schema and contract tools. OpenAPI validators check a response against the spec. Pact and similar tools let a consumer publish what it needs so the provider finds out before release, not after.
- Mocks and stubs. WireMock, Mockoon, or a small fake service you write yourself. Needed when a third party charges per call or is down more often than you are.
- A runner. Whatever already runs your unit tests. API tests are just tests, and they belong in the same pipeline stage as everything else fast and reliable.
- A record. Where the cases, their review state and their run history live. This is the piece teams forget, and the reason a suite ends up with 200 tests nobody can map to a requirement.
The honest advice is boring. Pick the library your team writes code in every day. The choice of client matters far less than whether the cases behind it are written down clearly enough for a second person to review.
What testing at the API layer cannot tell you
An API suite that passes proves the service behaves. It proves nothing about what the customer sees. Four things stay invisible from the kitchen window:
- Rendering and layout. A checkout total can be correct in the response and cut off on a narrow screen.
- Browser behaviour. Redirects, cookie handling, third party scripts and session expiry all live in the browser.
- Accessibility. Keyboard order and screen reader labels do not appear in a JSON payload.
- The journey. Whether a real person can find the button at all is not an API question.
So keep a small browser layer for the handful of journeys that pay the bills, and be deliberate about which checks run when. Sorting that out is mostly a naming problem, which is why the difference between smoke, sanity and regression runs is worth settling as a team. The wider picture of how these layers fit together is in our complete guide to software testing.
A five step plan you can start this sprint
You do not need a strategy document. You need ten good cases by Friday.
- List the endpoints. Take them from the OpenAPI spec, or from the route file if there is no spec. Most teams are surprised by the count.
- Rank by traffic and money. Login, checkout, and whatever the biggest customer calls hourly. Ten endpoints is enough to start.
- Write one functional case each. Valid input, exact status, two or three named fields in the response.
- Add negative cases for auth and validation. No token, expired token, another tenant's id, a missing required field. Expect four or five per endpoint.
- Put them in the record and wire the ids. Case id in the test name, results flowing back after every run.
That is a week of work for one person and it changes what a red build means. Instead of "something is broken somewhere", you get a case name that says which behaviour stopped being true.
If the expected result does not name a status code and a field, the case is not finished. It is a note.
Questions people ask
Is API testing the same as integration testing?
No. Integration testing is one kind of API testing, the kind that checks two or more services working together. A functional case against a single endpoint is an API test but not an integration test, and keeping the two labelled separately makes failures much faster to read.
Do I need to write cases down if the automated tests already exist?
Yes, if anyone other than the author needs to review, audit or replace them. Code tells you what is asserted. It does not tell you what behaviour was intended, which requirement it came from, or who agreed it was correct.
How many API tests should we have?
There is no ratio worth defending. A useful floor is one functional case and four negative cases for every endpoint a paying customer can reach, plus one contract check per consumer of that endpoint.
Can API tests replace end to end tests entirely?
No. They can replace most of them. Keep a small set of browser journeys for the flows that earn revenue, because rendering, redirects and accessibility never show up in a JSON response.
Where do performance checks fit?
In a separate suite, on separate infrastructure, with their own targets. Response time assertions inside a functional suite fail on slow build agents and get retried until they pass, which teaches the team to ignore them.