Postman
Explore, document, and automate APIs — from manual requests to CI with Newman.
What is Postman
Postman is an API platform that starts as a friendly GUI for sending requests and grows into full automated testing. You organize requests into Collections, parameterize them with Environments, add JavaScript test scripts, chain requests, and finally run the whole Collection in CI with the Newman CLI.
- Collections: grouped, saved, versionable requests (your test suite).
- Environments: named variable sets (local/staging/prod) referenced as {{baseUrl}}.
- Scripts: pre-request and test scripts in JavaScript using the pm.* API.
- Newman: run collections headless in CI and produce reports.
Target the TestForge API
Base URL http://localhost:3000/api. Try GET {{baseUrl}}/catalog/products first, then the authenticated cart and orders routes.
Setup: collection + environment
- Install the Postman desktop app (or use the web version).
- Create a Collection named "TestForge API".
- Create an Environment named "Local" with a variable baseUrl = http://localhost:3000/api.
- Add a request GET {{baseUrl}}/catalog/products?limit=5 and hit Send.
Variables make collections portable
Reference variables with double braces: {{baseUrl}}, {{accessToken}}. Switch environments to point the same requests at local vs. a deployed instance without editing them.
Writing tests (the Tests tab)
The Tests tab runs after the response arrives. Use pm.test() with pm.expect() (Chai assertions). Each pm.test becomes a pass/fail line in the results.
pm.test('status is 200', function () {
pm.response.to.have.status(200);
});
pm.test('returns an array of products', function () {
const body = pm.response.json();
pm.expect(body.data).to.be.an('array');
pm.expect(body.data.length).to.be.at.most(5);
});
pm.test('responds quickly', function () {
pm.expect(pm.response.responseTime).to.be.below(800);
});Auth and chaining requests
A classic flow: log in, save the token to an environment variable in the login request’s Tests tab, then reference it on later requests. This chains requests without copy-pasting tokens.
// Request body (raw JSON):
// { "email": "customer@testforge.dev", "password": "Password123!" }
pm.test('login succeeds', function () {
pm.response.to.have.status(200);
});
const body = pm.response.json();
pm.environment.set('accessToken', body.accessToken);
pm.environment.set('refreshToken', body.refreshToken);On the protected request (e.g. POST {{baseUrl}}/cart/items), set the Authorization header to Bearer {{accessToken}} — or configure it once at the Collection level under the Authorization tab so every request inherits it.
Authorization: Bearer {{accessToken}}
Content-Type: application/json
Body (raw JSON):
{ "productId": "{{productId}}", "quantity": 1 }Pre-request scripts
Pre-request scripts run before the request is sent — useful for computing timestamps, generating data, or grabbing an id from a previous call. You can even send an HTTP call with pm.sendRequest to fetch a fresh token first.
// Fetch a product id to use in this request
pm.sendRequest(pm.environment.get('baseUrl') + '/catalog/products?limit=1', function (err, res) {
if (!err) {
const id = res.json().data[0].id;
pm.environment.set('productId', id);
}
});Data-driven runs with the Collection Runner
The Collection Runner executes every request in order and can iterate over a CSV/JSON data file, substituting {{variables}} per row — great for testing many inputs.
[
{ "email": "customer@testforge.dev", "password": "Password123!", "expected": 200 },
{ "email": "customer@testforge.dev", "password": "wrong", "expected": 401 }
]pm.test('status matches expected for this row', function () {
pm.response.to.have.status(Number(pm.iterationData.get('expected')));
});Automating in CI with Newman
Export the Collection and Environment to JSON, commit them, and run headless with Newman. This turns your Postman suite into a CI gate.
npm install -g newman
newman run TestForge_API.postman_collection.json \
-e Local.postman_environment.json \
--reporters cli,junit \
--reporter-junit-export results.xmlname: newman
on: [push, pull_request]
jobs:
api:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with: { node-version: 20 }
- run: npm install -g newman
- run: newman run TestForge_API.postman_collection.json -e Local.postman_environment.jsonBest practices
- Parameterize everything with environment/collection variables — no hard-coded URLs or tokens.
- Set auth once at the collection level so requests inherit it.
- Keep secrets in environment variables marked as "secret", and do not commit real credentials.
- Write at least one assertion per request (status + one body check).
- Version the exported collection/environment JSON in git so changes are reviewable.
- Run via Newman in CI — the GUI is for authoring, Newman is for gating.
Environments, variables & scripts
Postman resolves {{variables}} against several scopes, each with a different lifetime. Understanding the order matters because a name defined in more than one scope resolves to the narrowest one that is set.
- Global: available across every collection and environment; the widest, longest-lived scope — use sparingly.
- Collection: shared by all requests in one collection (pm.collectionVariables), good for a baseUrl that belongs to one API.
- Environment: a named, switchable set (Local / Staging / Prod) via pm.environment — the workhorse scope.
- Local: created inside a script for the current request/run only; never persisted.
- Data: columns from the CSV/JSON file the Collection Runner iterates over (pm.iterationData) — read-only, one row at a time.
Precedence (narrowest wins)
When the same name exists in multiple scopes, Postman resolves it as: data > local > environment > collection > global. So an environment value overrides a collection value, and a data-file column overrides both.
Pre-request scripts run before the request is sent (set up data, compute a signature, refresh a token). Test scripts run after the response arrives and hold your assertions with pm.test() + pm.expect(). Persisting a value with pm.environment.set() in a test script is exactly how you hand a login token to later requests.
// Request body (raw JSON):
// { "email": "customer@testforge.dev", "password": "Password123!" }
pm.test('login returns 200', function () {
pm.response.to.have.status(200);
});
pm.test('response carries an access token', function () {
const body = pm.response.json();
pm.expect(body).to.have.property('accessToken');
// Persist for later requests in this environment
pm.environment.set('accessToken', body.accessToken);
pm.environment.set('refreshToken', body.refreshToken);
});// Local variable: exists only for this request execution
const nonce = 'req-' + Date.now();
pm.variables.set('nonce', nonce);
// Read the token saved by the login request
const token = pm.environment.get('accessToken');
if (!token) {
console.warn('No accessToken set — run POST /auth/login first');
}Keep secrets out of git
Mark token/password variables as "secret" type and store them in the environment, not the collection. Exported collection JSON is committed; exported environment JSON with real credentials should not be.
Chaining requests & data-driven runs
Real API tests are workflows, not single calls. Postman chains requests two ways: capture a value in one request and reference it in the next (via a shared variable), or fetch something inline with pm.sendRequest before the current request even fires.
// Grab a real product id so the cart request has something to add
pm.sendRequest(
{
url: pm.environment.get('baseUrl') + '/catalog/products?limit=1',
method: 'GET',
},
function (err, res) {
if (err) { return; }
const id = res.json().data[0].id;
pm.environment.set('productId', id);
}
);To control ordering inside a Collection Runner run, use postman.setNextRequest() to jump to a named request (or stop the run). By default the runner walks requests top to bottom; setNextRequest lets you loop, skip, or branch — for example, add to cart, then jump straight to checkout.
pm.test('item added to cart', function () {
pm.response.to.have.status(201);
});
if (pm.response.code === 201) {
postman.setNextRequest('Checkout order');
} else {
// Stop the run so we don't attempt checkout on an empty cart
postman.setNextRequest(null);
}Data-driven testing runs the whole collection once per row of a data file. Attach a CSV or JSON file in the Collection Runner; each row exposes its columns as pm.iterationData, and any {{column}} in a URL, header, or body is substituted per iteration.
[
{ "email": "customer@testforge.dev", "password": "Password123!", "expected": 200 },
{ "email": "customer@testforge.dev", "password": "wrong", "expected": 401 },
{ "email": "nobody@testforge.dev", "password": "Password123!", "expected": 401 }
]pm.test('status matches the expected code for this row', function () {
const expected = Number(pm.iterationData.get('expected'));
pm.response.to.have.status(expected);
});One collection, many inputs
A CSV of email/password/expected columns turns a single login request into a full positive/negative matrix — no duplicated requests, and new cases are just new rows.
Newman & CI
Newman is the command-line runner for Postman collections. Export the collection and environment to JSON, commit them, and Newman runs the exact same requests and test scripts headless — so your Postman suite becomes a CI gate that fails the build on a broken API.
npm install -g newman
# Run a collection against the Local environment
newman run TestForge_API.postman_collection.json \
-e Local.postman_environment.json
# Data-driven run: one iteration per row of the CSV/JSON file
newman run TestForge_API.postman_collection.json \
-e Local.postman_environment.json \
-d login-cases.jsonReporters control the output. The cli reporter prints to the terminal; junit produces XML that CI can parse into test results; the htmlextra reporter (installed separately) gives a rich shareable report. You can emit several at once.
# CLI output plus a JUnit XML file for CI to ingest
newman run TestForge_API.postman_collection.json \
-e Local.postman_environment.json \
--reporters cli,junit \
--reporter-junit-export results.xml
# Rich HTML report (npm install -g newman-reporter-htmlextra first)
newman run TestForge_API.postman_collection.json \
-e Local.postman_environment.json \
--reporters cli,htmlextra \
--reporter-htmlextra-export report.html# In a GitHub Actions job, after starting the API:
npm install -g newman
newman run TestForge_API.postman_collection.json \
-e CI.postman_environment.json \
--reporters cli,junit \
--reporter-junit-export results.xml
# A non-zero exit code (any failed assertion) fails the CI job.Mock servers & monitors
Two related hosted features: a Mock Server returns example responses so a frontend can develop before the real API exists, and a Monitor runs a collection on a schedule from Postman cloud (e.g. every 15 minutes) to catch outages and alert you. Both complement, but do not replace, Newman in CI.
Interview questions & answers
Postman has several variable scopes. Name them and tell me which one wins when the same variable is defined in more than one.
The scopes are global, collection, environment, local, and data. Precedence goes from narrowest to widest: data > local > environment > collection > global. So if baseUrl exists in both the environment and the collection, the environment value is used; a data-file column would override both. This lets you set broad defaults at the collection level and selectively override them per environment or per data row.
What is the difference between a pre-request script and a test script?
A pre-request script runs before the request is sent, so it is used for setup — computing timestamps, generating a request body, signing a payload, or fetching a fresh token with pm.sendRequest. A test script runs after the response arrives and is where your assertions live (pm.test / pm.expect) and where you persist values from the response into variables. In short: pre-request prepares the call, test verifies and captures the result.
How do you assert on a response in Postman? Walk me through pm.test and pm.expect.
pm.test(name, fn) registers a named assertion block; each one shows up as a pass/fail line in the results and in Newman output. Inside it you use pm.expect(), which is the Chai BDD assertion library, so you can write things like pm.expect(body.data).to.be.an("array") or pm.response.to.have.status(200). Grouping related assertions in one pm.test with a clear name makes failures easy to read, and any thrown assertion marks that test as failed without stopping the rest.
You log in once and need that token on ten other requests. How do you capture and reuse it?
In the login request's test script, read the token from the response and store it with pm.environment.set("accessToken", body.accessToken). Later requests reference it as {{accessToken}} — usually by setting Bearer {{accessToken}} once at the collection's Authorization tab so every request inherits it. Because it lives in the environment, switching environments swaps the token automatically, and you never copy-paste credentials between requests.
How do you control the order requests run in during a collection run?
By default the Collection Runner executes requests top to bottom. To change that flow you call postman.setNextRequest("Request name") from a script to jump to a specific request, or postman.setNextRequest(null) to stop the run. This lets you branch — for example, only proceed to checkout if the add-to-cart request returned 201 — or loop over a request until a condition is met. It only affects runner/Newman execution, not manually sending a single request.
Explain data-driven testing with the Collection Runner.
You attach a CSV or JSON data file to the Collection Runner, and it runs the whole collection once per row (iteration). Each row's columns are exposed as pm.iterationData, and any {{column}} used in a URL, header, or body is substituted for that iteration. This turns a single login request into a full positive/negative matrix — valid credentials, wrong password, unknown user — driven entirely by rows in the file, so adding a case is just adding a row rather than duplicating requests.
What is Newman and how does it fit into CI?
Newman is Postman's command-line collection runner. You export the collection and environment to JSON, commit them, and run newman run collection.json -e env.json in a CI job, optionally with -d for a data file. It executes the same requests and test scripts as the GUI and exits non-zero if any assertion fails, which fails the pipeline. Adding reporters like --reporters cli,junit produces machine-readable output CI can display as test results.
When would you use an environment versus globals?
Use an environment for values that change per target — baseUrl, tokens, ids — because environments are switchable, so the same collection can point at Local, Staging, or Prod just by changing the selected environment. Globals are a single flat set shared across everything, with no notion of "which target," so they are easy to collide on and hard to reason about. The practical rule is: environment-scope anything target-specific and keep globals nearly empty, reserving them for a rare truly cross-everything constant.
What are mock servers and monitors, and when would you reach for each?
A mock server returns predefined example responses for your requests, so a frontend or consumer can be built and tested before the real API exists or when it is unstable. A monitor runs a collection on a schedule from Postman's cloud — say every fifteen minutes — and alerts you if requests fail, giving you lightweight uptime and contract checking in production. They solve different problems: mocks unblock development, monitors provide ongoing observability, and neither replaces running the suite in CI on every change.
How do you keep a large collection maintainable over time?
Group requests into folders that mirror the API or a user journey, parameterize everything with variables so there are no hard-coded URLs or tokens, and set auth once at the collection level so requests inherit it. Push shared setup into collection- or folder-level pre-request scripts instead of duplicating it, name requests and pm.test blocks descriptively, and keep secrets in secret-type environment variables that are not committed. Finally, version the exported collection JSON in git so changes are reviewed like code, and run it through Newman in CI so drift is caught automatically.
Where to go next
- Docs: learning.postman.com
- Explore the pm.* API (pm.response, pm.expect, pm.collectionVariables, pm.sendRequest).
- Practice: build a TestForge collection covering login → browse → add to cart → checkout, chaining ids and tokens between requests, then run it with Newman.