DEV Community

Cover image for Create, Then Verify: Turning Postman Requests into Real Workflow Tests
Imran Al Munyeem
Imran Al Munyeem

Posted on Originally published at imranalmunyeem.github.io

Create, Then Verify: Turning Postman Requests into Real Workflow Tests

A DELETE endpoint that returns 204 No Content and deletes nothing will pass every single-request test you write.

The status code is right. The response time is fine. The body is empty, as specified. Your suite is green — and the record is still in the database, because the only way to know deletion worked is to ask again: a follow-up GET that must return 404.

That's the behaviour layer of API testing, and it can't be reached one request at a time. It needs chained requests — a sequence where each step feeds the next, testing the resource's whole lifecycle. In Postman the bridge between steps is one line of script. Here's the full pattern.

The bridge: capture, then reference

Everything hangs on one move — capture a value from a response, use it in the next request's URL or body.

Request 1 — POST /users (Scripts → Post-response):

pm.test("User created", () => pm.response.to.have.status(201));

const created = pm.response.json();
pm.collectionVariables.set("newUserId", created.id);
Enter fullscreen mode Exit fullscreen mode

Request 2 — GET {{baseUrl}}/users/{{newUserId}}:

pm.test("Created user is retrievable", () => {
    pm.response.to.have.status(200);
    pm.expect(pm.response.json().id)
        .to.eql(pm.collectionVariables.get("newUserId"));
});
Enter fullscreen mode Exit fullscreen mode

Run the collection (the Runner executes top to bottom) and the two requests are now one test: creation actually persisted.

Why pm.collectionVariables and not pm.environment? The captured ID is run-state, not configuration — it belongs to the suite, not to "staging vs production". Keeping run-state in collection scope also means the chain works no matter which environment is selected.

The full lifecycle chain

The canonical workflow folder — five requests, each verifying the last:

📁 User lifecycle
   1. POST   /users            → 201, capture newUserId
   2. GET    /users/{{newUserId}} → 200, fields match what was sent
   3. PATCH  /users/{{newUserId}} → 200, capture nothing, send {"name": "Updated"}
   4. GET    /users/{{newUserId}} → 200, name is "Updated", other fields unchanged
   5. DELETE /users/{{newUserId}} → 204
   6. GET    /users/{{newUserId}} → 404  ← the test that actually proves deletion
Enter fullscreen mode Exit fullscreen mode

Step 4 deserves its subtle assertion: not just that the patched field changed, but that the others didn't — PATCH endpoints that quietly reset unrelated fields are a classic, expensive bug:

const user = pm.response.json();
pm.test("PATCH changed only the name", () => {
    pm.expect(user.name).to.eql("Updated");
    pm.expect(user.email).to.eql(pm.collectionVariables.get("originalEmail"));
});
Enter fullscreen mode Exit fullscreen mode

(Capture originalEmail back in step 1, same one-liner pattern.)

Fresh data every run

A chain that creates test@example.com works exactly once against a persistent database — the second run collides with the first run's leftovers. Postman's dynamic variables fix this with zero code:

{
  "name": "{{$randomFullName}}",
  "email": "{{$randomEmail}}",
  "ref": "{{$guid}}"
}
Enter fullscreen mode Exit fullscreen mode

Every run creates unique records. And the companion discipline: tests that create data should delete it — which the lifecycle chain does by design, steps 5–6. A suite that cleans up after itself is a suite you can run any time, from anywhere, including every 15 minutes from CI. Re-runnability is the cardinal virtue of automation.

Controlling the flow

By default the Runner goes top to bottom. Scripts can redirect it:

// Skip to teardown if creation failed — no point testing a ghost
if (pm.response.code !== 201) {
    pm.execution.setNextRequest("Cleanup");
}

// Or end the run entirely
pm.execution.setNextRequest(null);
Enter fullscreen mode Exit fullscreen mode

(postman.setNextRequest is the legacy name; pm.execution.setNextRequest is current.) Use it sparingly — a suite that jumps around is hard to read — but skip-on-failure and polling loops are exactly what it's for.

Keep chains short

One warning from maintenance experience: chains are powerful and chains are coupling. A folder where request 6 depends on request 1 is fine; a collection where request 40 depends on request 3 is unmaintainable. The discipline that scales: each workflow folder is self-contained — it creates what it needs, verifies behaviour, and destroys what it made. Folders can run independently; requests within a folder cannot. That's the right amount of coupling.


Adapted from Chapter 7 of my free, open-source book *API Testing Using Postman: The Practical Guide to Modern API Testing*. Read online, grab the PDF/EPUB, or contribute on GitHub.

I'm a PhD researcher in Computer Science at Nottingham Trent University working on cybersecurity and AI-assisted security testing. More at imranalmunyeem.com.

Top comments (0)