How to Test an API: A Short Walkthrough

How to test an API in three moves: send one request and read the response, chain calls into a flow, then run the flow on every pull request.

Mustafa BayramogluMustafa BayramogluAbstract illustration of a single request line widening into a chain of nodes that loops back

To test an API, send a request to one endpoint and read the status, headers and body of the answer. Then chain the calls a real user makes, such as a login, a create and a read, and run that chain on every pull request. This walkthrough does it in three moves against a small shop API, with a curl command for each move, and ends with the same flow as a YAML test that runs in CI.

The short version: three moves

To see what API testing covers before you start, read what API testing is.

The three moves, in order:

  1. Test one endpoint by hand and learn what a correct answer looks like.
  2. Chain the calls into a flow: log in, create something, read it back, and send one request that must be refused.
  3. Run the flow on every pull request, so the checking repeats without you.

Each move builds on the one before. A flow made of calls you have not checked one by one only hides which call is broken. The longer treatment, with more cases per step, is REST API testing, step by step. This post is the short path through the same ground.

Move 1: Test one endpoint by hand

Set the base URL once, then send a GET:

export APP_URL=http://localhost:3000
curl -i "$APP_URL/api/orders/1"

The -i flag makes curl print the status line and the headers above the body. A response has those three parts, and this first request is you learning what correct looks like. Write down the status, the content type and the fields in the body. Those notes become your checks.

For a good answer on a route that exists, the status is 200 OK, which means the request succeeded. Assert the exact code, not “any 2xx”. If a create route answers 201 Created, the request has led to the creation of a resource. A change from 201 to 200 is then worth a failing test.

Next, the body:

HTTP/1.1 200 OK
Content-Type: application/json

{"id":1,"productId":42,"quantity":2,"status":"paid"}

Check that the fields you rely on exist and have the right type, and compare the values you sent. Leave out values that change on every run, such as ids and timestamps. For those, check only that the value is present and has the right shape.

Move 2: Chain the calls into a flow

A real user does not send one request. They log in, then create something, then look at it. Carry the token from the login response into the next request:

TOKEN=$(curl -s -X POST "$APP_URL/api/auth/login" \
  -H 'Content-Type: application/json' \
  -d "{\"email\":\"$TEST_EMAIL\",\"password\":\"$TEST_PASSWORD\"}" | jq -r .token)

curl -i -X POST "$APP_URL/api/orders" \
  -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
  -d '{"productId":42,"quantity":2}'

curl -s "$APP_URL/api/orders" -H "Authorization: Bearer $TOKEN"

The create should answer 201. The last call is the one that matters. A create can return a success code and store nothing, so follow every write with a read that must hold the new item. Stresseur’s generated flows do the same: after a create (POST, PUT or PATCH), a later read of the list or page must hold the new id, or a value the user typed, made unique per run.

Then break it on purpose. Send the same orders request with no token and expect a 401, the code for a request that lacks valid authentication credentials. Send a quantity of -1 and expect a 400, the code for a request the server refused as malformed. Ask for an order id that does not exist and expect a 404, which means the server cannot find the requested resource. After the 400, list the orders again and check that the bad request saved nothing.

Move 3: Run the flow on every pull request

Typing curl proves the API works once. To repeat it, record the flow. Stresseur records how an API is really used during development, turns those flows into YAML tests, and runs them in CI on every pull request. The quickstart is npx stresseur init, which finds the app, starting its dev script if needed. With --demo it runs on a built-in sample shop.

The flows live in the repo under api-tests/ as *.yamlflow.yaml files. They read the app’s base URL from APP_URL, the variable you already used above.

To run them in CI, init-ci writes the GitHub Actions workflow. The workflow runs each flow 13 times, one warm-up and 12 measured. Each pull request then gets one check, “Stresseur / performance”, and one comment.

For the full setup, read API tests in GitHub Actions. For the product side, see API testing with Stresseur.

When to change the order

The order suits an API you have not tested before. Change it when your situation differs. If you have not picked a tool for these moves yet, the guide to choosing an API testing framework compares the options.

  • A public read-only API. Skip the login and the create. Put the weight on the status and body checks from move 1.
  • An API that changes daily. Stay with moves 1 and 2 by hand until its shape settles, then record the flow. A test you rewrite every day gets switched off.

Conclusion

Testing an API is a short loop. Check one endpoint by hand until you know what correct looks like, chain the calls a user makes and break them on purpose, then run the flow on every pull request. Start with a login and one create-and-read path, and add a test when a real failure shows what was missing.

Frequently asked questions

How can I test an API?

Send a request to one endpoint with curl and read the status, headers and body. Then chain the calls a real user makes, and run them on every pull request.

What should I check in an API response?

Check three things: the exact status code, the Content-Type header, and the body fields you rely on. Leave out values that change on every run, such as ids and timestamps.

Why read the item back after a create?

A create can return a success code and store nothing, so only a later read shows whether the item exists. Stresseur's generated flows do this: after a create, a later read of the list or page must hold the new id, or a value the user typed.

When should I stop testing by hand and record a flow?

When you know what a correct answer looks like and the shape of the API has settled. A test you rewrite every day gets switched off. Then `npx stresseur init` finds the app and records the flow.

How many requests does a first API test need?

One. Send a single GET with `curl -i` and read the status, headers and body. Chain more calls only once that one is understood. In CI, each recorded flow then runs 13 times, one warm-up and 12 measured.

All posts