Skip to content
routebase
API Testing12 chapters

Chapter 04 of 12

API Integration Testing With Scenarios

Testing multi-step flows where each call depends on the last, covering state between steps, asynchronous work, conditional branches and idempotency.

Single-request tests prove that each endpoint works on its own, and that is a genuinely useful thing to know. They cannot prove that the endpoints work together, which is what API integration testing is for, and a surprising share of production defects live in exactly that gap.

A resource is created and comes back with an identifier the read endpoint does not accept. A token is issued and then rejected by the very service that issued it. A status field moves to processing and never leaves. Every one of those passes a per-endpoint suite and fails the first real client.

What a workflow test actually tests

The unit under test is the sequence, not the individual request, and that changes what the assertions are for. A workflow test asserts three things that a single call structurally cannot.

State carries forward. The identifier the create call returned is the identifier the read call accepts, in the same format, without a transformation nobody documented.

Order is respected. The service rejects the steps that should not be possible yet. Reading a resource before it exists returns 404 and not an empty success, and confirming an order before payment returns a refusal.

The flow terminates. The last step reaches the state the business expects, and that assertion is what gives the whole scenario its point.

Designing a scenario worth having

Start from a sentence a non-engineer would recognise. "A new customer signs up, adds an item to a basket, pays, and receives an order confirmation" is a scenario. "Test the orders API" is not, and it produces a scenario that drifts into a second functional suite.

Then keep it to the steps that carry the story. A scenario with thirty steps takes a long time to run, breaks often, and tells you almost nothing when it breaks, because the failure could be anywhere. Six to ten steps is a comfortable range for a flow you want people to trust.

Each step should still assert. It is tempting to let intermediate steps pass on any 2xx and put all the assertions at the end. That produces a scenario which fails at step nine for something that went wrong at step three. Assert enough on each step to localise a failure to the step that caused it.

Passing values between steps

The mechanism is extraction, which means a step captures something from its response into a named variable that later steps can reference.

A value extracted in one step is available to every later step, and a step whose variable no earlier step sets is the failure a reorder creates.
Step 1  POST /customers        extract  $.id              →  customerId
Step 2  POST /baskets          extract  $.id              →  basketId
        body { "customerId": "{{customerId}}" }
Step 3  POST /baskets/{{basketId}}/items
Step 4  POST /orders           extract  $.id              →  orderId
        body { "basketId": "{{basketId}}" }
Step 5  GET  /orders/{{orderId}}
        assert  $.status  equals  confirmed

Two failure modes show up before you have built many of these.

The first is a variable that no earlier step produces, which usually means somebody reordered the steps. The symptom is a request with an unresolved placeholder in it, and the fix is to check the direction of every reference after any reorder.

The second is a variable that carries a value from the previous run. That happens when variables persist beyond the scenario, and it makes a broken run look green because the old identifier still resolves to a real resource. Variables scoped to the run avoid it entirely.

Waiting for asynchronous work

Plenty of APIs accept a request, return 202, and do the work later. Testing those means waiting, and how you wait decides whether the test is stable.

ApproachWhen it worksWhat it costs
Poll a status resource until it is terminalThe API exposes progress, as most doA few extra requests, and a deadline you have to choose
Wait a fixed interval, then assertNo status resource existsThe interval that works today is the flake of next quarter
Subscribe to a webhook and assert on deliveryThe API pushes, and your runner can receiveSetup, plus a receiving endpoint the test can read

Polling is almost always the right answer where it is available. Give the loop a deadline and fail with a clear message when it expires. A failure reading "timed out waiting for order 4f2 to leave processing after 30 s" is far more useful than an assertion mismatch on a field that was never going to be filled in time.

A fixed wait is a legitimate tool as long as it stays bounded and short. The mistake is treating it as a fix for a race condition instead of a placeholder until a status resource exists.

Conditional steps

Some flows branch, and a scenario that assumes only one branch will fail half the time against a realistic environment. A payment either succeeds outright or comes back asking for a second factor.

A conditional step evaluates a variable an earlier step captured and decides whether to run. The most useful variable is usually the previous status code, because it says what happened and not what was supposed to.

Keep the conditions simple, because one comparison per step is enough for the branches worth testing. A scenario that needs nested logic is usually two scenarios that would be clearer written separately.

Idempotency deserves its own scenario

Retries are not an edge case, because networks drop connections and clients give up waiting. A client times out, resends the same request, and the question is whether your API charged the card twice.

The test is short and the assertion is easy to get wrong. Send the request, send it again with the same idempotency key, and then check the collection. A second 200 proves nothing on its own, because a duplicate resource also answers 200. What proves it is the count, so read back the collection and assert that exactly one record exists.

Run the same scenario with a different key and assert that two records now exist, because an implementation that deduplicates everything is broken in the opposite direction.

In Routebase

A scenario in Routebase is an ordered list of steps, and each step is a reference to an existing test case, never a copy of one. Edit the case and every scenario using it picks up the change, and that is what keeps a growing set of flows maintainable.

Scenario editor listing ten ordered steps, including a wait of 500 milliseconds, a wait of 4 seconds and a final step marked conditional.
A scenario chains ten steps, each one a reference to an existing case, with two waits and one conditional step.

Steps pass values through the response extractions defined on their cases, so a step that extracts $.id into orderId makes {{orderId}} available to every later step in URLs, headers, bodies, assertions and conditions. The step dialog lists Variables in and Variables out for each step, and it flags a variable that no earlier step sets, which catches a reorder before the run does.

Wait steps insert a bounded pause of up to 300,000 milliseconds where an API needs one, and they show up in the results as their own row. Conditional steps compare a variable against a value with one of six operators and either execute or skip, with the skip reason shown on the result row. Run settings decide whether a failure stops the scenario, is ignored, or aborts the run, so a diagnostic pass can continue past the first red step.

The results table shows one row per step with its status, the failure or skip reason inline, the variables it consumed and the ones it captured. A failing run opens on the first failed step, not at the top. Details are in the Test Scenarios guide.

Frequently asked questions

What is a workflow test in API testing?

A workflow test runs several API calls in order and passes values from one response into the next request. It proves that a business flow such as register, log in, create an order and read it back holds together end to end. Single-request tests cannot see these defects, because the failure only appears when the calls are combined.

How do you test an asynchronous API?

Send the request that starts the work, capture whatever handle the response gives you, then poll the status resource until it reports completion or until a deadline passes. Where the API offers no status resource, a bounded wait between steps is the honest fallback. Assert on the terminal state rather than on the wait itself, because a fixed sleep that happens to be long enough today becomes a flaky test tomorrow.

How do you test idempotency in an API?

Send the same request twice with the same idempotency key and assert that the second call returns the same result without creating a second resource. The check that carries the weight is a count, so read the collection afterwards and assert that exactly one record exists. A second 200 alone proves nothing, because a duplicate resource can also answer 200.

Should every endpoint be covered by a workflow test?

No. Workflow tests are slower, they depend on more state, and they fail for more reasons than a single-request test does. Use them for the flows that carry the business, which is usually signup, authentication, the primary create-and-read path and payment. Everything else is better served by functional and contract tests on the individual endpoints.

Last reviewed by The Routebase Team.

Ready to ship on it?

Routebase is live. Design your API once — docs, mocks, tests, and monitoring all follow from the same source.

14-day Pro trial — no credit card required.