Skip to content
SDETRole
SDETRole

Lesson 5 of 9 · API Testing

Postman: Collections, Environments, Scripts

Organise requests into collections, switch targets with environments, write pm.test scripts, chain a login token and run the lot in CI with Newman.

beginner 7 min read Updated

You will learn to

  • Organise requests into collections and folders with shared auth and variables
  • Use environments and variable scopes to run the same requests against different targets
  • Write post-response tests with pm.test and pm.expect and chain values between requests
  • Run a collection from the command line with Newman, including data files and JUnit output

Prerequisites

  • HTTP Fundamentals
  • API Authentication

Postman is usually the first API tool a tester learns, and for good reason: you can send a request, read the response and save it in a minute. Its real value for a team, though, comes from three features many people never use properly: collections that model an API, environments that point the same collection at different servers, and scripts that turn saved requests into automated tests you can run in CI.

This lesson builds a small collection against the SDETRole Practice API at https://sdetrole.com/api/mock. Everything here works in the free Postman app.

Collections and folders

A collection is a saved, ordered group of requests. Treat it as an executable description of an API, not a scrapbook. A sensible structure mirrors the resources:

SDETRole Practice API
├── Auth
│   ├── Login (POST /auth/login)
│   └── Me (GET /auth/me)
├── Users
│   ├── List users (GET /users?page=1&limit=10)
│   ├── Get user (GET /users/1)
│   └── Create user (POST /users)
└── Negative
    ├── Me without token
    └── Create user with duplicate email

Collections and folders can hold settings that every child request inherits: authorisation, variables, and scripts. Setting auth once at collection level (type Bearer Token, token value {{token}}) and leaving each request on Inherit auth from parent means a scheme change is a one-line edit.

Collections are exported as JSON (Collection Format v2.1), which you can commit to Git next to your code.

Variables and environments

Hard-coded URLs and tokens are the first thing that breaks a shared collection. Replace them with variables, written as {{name}} in the URL, headers or body:

GET {{baseUrl}}/users/1
Authorization: Bearer {{token}}

Postman resolves a variable from the narrowest scope that defines it:

Scope Lives in Typical use
Local One script run Temporary values set with pm.variables.set
Data The current row of a data file Data-driven runs
Environment The selected environment baseUrl, credentials, tokens per target
Collection The collection Values that are the same everywhere, such as an API version
Global The workspace Rarely a good idea; easy to leak between collections

An environment is a named set of variables you switch with a dropdown. Create one called Practice:

Variable Value
baseUrl https://sdetrole.com/api/mock
username tester
password practice123
token (empty; set by a script)

A Staging environment would hold the same variable names with different values. The requests never change.

Writing tests in post-response scripts

Every request has a Scripts tab with two sections: Pre-request (runs before sending) and Post-response (runs after the response arrives; older versions called this the Tests tab). Scripts are JavaScript using the pm object, with Chai-style assertions through pm.expect.

For POST {{baseUrl}}/auth/login with body {"username":"{{username}}","password":"{{password}}"}:

pm.test('status is 200', () => {
  pm.response.to.have.status(200);
});

pm.test('returns a bearer token', () => {
  const body = pm.response.json();
  pm.expect(body.tokenType).to.eql('Bearer');
  pm.expect(body.accessToken).to.be.a('string').and.not.be.empty;
  pm.expect(body.expiresIn).to.eql(3600);

  // Chain: save the token for every later request
  pm.environment.set('token', body.accessToken);
});

pm.test('responds quickly', () => {
  pm.expect(pm.response.responseTime).to.be.below(1000);
});

For GET {{baseUrl}}/users?page=1&limit=10, check the body and the pagination headers the Practice API sends:

pm.test('first page of users', () => {
  pm.response.to.have.status(200);
  pm.expect(pm.response.headers.get('Content-Type')).to.include('application/json');

  const body = pm.response.json();
  pm.expect(body.data).to.have.lengthOf(10);
  pm.expect(body.page).to.eql(1);
  pm.expect(body.total).to.eql(25);
  pm.expect(body.totalPages).to.eql(3);
});

pm.test('pagination headers', () => {
  pm.expect(pm.response.headers.get('X-Total-Count')).to.eql('25');
  pm.expect(pm.response.headers.get('Link')).to.include('rel="next"');
});

Header values are always strings, which is why X-Total-Count is compared with '25', not 25.

Negative tests are just as easy. For GET {{baseUrl}}/auth/me with auth set to No Auth:

pm.test('rejects a missing token', () => {
  pm.response.to.have.status(401);
  pm.response.to.have.header('WWW-Authenticate');

  const problem = pm.response.json();
  pm.expect(problem.title).to.eql('Unauthorized');
  pm.expect(problem.detail).to.eql('Missing Authorization header.');
});

For structure checks, pm.response.to.have.jsonSchema(schema) validates the body against a JSON Schema object you define in the script. Draft and test schemas first with the JSON Schema Validator.

Pre-request scripts and getting a token automatically

Relying on someone running Login first is fragile. A collection-level pre-request script can fetch a token when there is none:

// Collection > Scripts > Pre-request
if (!pm.environment.get('token')) {
  pm.sendRequest({
    url: pm.variables.get('baseUrl') + '/auth/login',
    method: 'POST',
    header: { 'Content-Type': 'application/json' },
    body: {
      mode: 'raw',
      raw: JSON.stringify({
        username: pm.variables.get('username'),
        password: pm.variables.get('password'),
      }),
    },
  }, (err, res) => {
    if (err) {
      console.error('Login failed', err);
      return;
    }
    pm.environment.set('token', res.json().accessToken);
  });
}

pm.variables.get resolves a name through all scopes, so the script works whether baseUrl lives in the environment or the collection. On a real system, also store the expiry time and refresh the token when it is close to expiring. Use the Postman Console (View menu) to see console.log output and the raw requests actually sent.

Running collections and data-driven tests

The Collection Runner runs every request in order and reports each pm.test. It also accepts a CSV or JSON data file: the run repeats once per row, and each column becomes a data-scope variable.

users.csv:

name,email,role,expectedStatus
Test User,test.user@example.com,tester,201
Priya Shah,priya.shah@example.com,tester,409
A,not-an-email,tester,422
Valid Name,valid.name@example.com,superuser,422

The Create user request body uses the columns:

{ "name": "{{name}}", "email": "{{email}}", "role": "{{role}}" }

And its post-response script reads the expected outcome from the same row:

const expected = Number(pm.iterationData.get('expectedStatus'));

pm.test(`${pm.iterationData.get('email')} returns ${expected}`, () => {
  pm.response.to.have.status(expected);
  if (expected === 201) {
    pm.response.to.have.header('Location');
  } else {
    pm.expect(pm.response.headers.get('Content-Type')).to.include('application/problem+json');
  }
});

The second row returns 409 Conflict because priya.shah@example.com already exists in the seed data; the last two return 422 Unprocessable Content with an errors array naming each invalid field.

Newman: Postman in CI

Newman is the open-source command-line runner for Postman collections. Export the collection and environment as JSON, then:

npm install -g newman

newman run sdetrole-practice.postman_collection.json \
  -e practice.postman_environment.json \
  --env-var "password=$API_PASSWORD" \
  -d users.csv \
  --reporters cli,junit \
  --reporter-junit-export results/newman-junit.xml
  • -e selects the environment file; --env-var overrides a variable from a CI secret so it is never stored in the file.
  • -d supplies the data file.
  • The JUnit reporter writes XML that almost every CI server can display as a test report.
  • Newman exits with a non-zero code when any test fails, which fails the CI job.

Postman also ships its own Postman CLI, which can run collections stored in your Postman workspace. Newman is the better fit when the collection JSON lives in your Git repository.

Practice exercise

Build a Postman collection against the Practice API with an environment named Practice, containing:

  1. A Login request that saves the token to the environment.
  2. A Me request that inherits bearer auth from the collection and checks the role and scopes.
  3. A Me without token request that checks the 401 response, detail and WWW-Authenticate header.
  4. A Get user request for /users/1 that checks the status, Content-Type and that the ETag header is present.

Run it in the Collection Runner, then with Newman. You can try each request first in the API Playground.

Show a model answer

Collection settings: Authorization type Bearer Token, token {{token}}. Me without token overrides this with No Auth.

Me post-response script:

pm.test('returns the logged-in tester', () => {
  pm.response.to.have.status(200);
  const me = pm.response.json();
  pm.expect(me.username).to.eql('tester');
  pm.expect(me.role).to.eql('tester');
  pm.expect(me.scopes).to.include('users:read');
});

Get user post-response script:

pm.test('user 1 with caching metadata', () => {
  pm.response.to.have.status(200);
  pm.expect(pm.response.headers.get('Content-Type')).to.include('application/json');
  pm.response.to.have.header('ETag');

  const user = pm.response.json();
  pm.expect(user).to.include.keys('id', 'name', 'email', 'role', 'active', 'createdAt');
  pm.expect(user.id).to.eql(1);
});

Newman command:

newman run sdetrole-practice.postman_collection.json -e practice.postman_environment.json --reporters cli,junit --reporter-junit-export results/newman.xml

Run Login first in the collection order, or add the collection-level pre-request script from this lesson so order no longer matters.

Key takeaways

  • A collection models an API; folders and collection-level auth, variables and scripts remove duplication.
  • Environments let one collection target many servers. Keep secrets in current values or Vault, never in exported initial values.
  • Post-response scripts use pm.test, pm.expect and pm.response.to.have.status; chain values with pm.environment.set.
  • Data files plus pm.iterationData turn one request into a table-driven test.
  • Newman runs exported collections in CI with JUnit output and a failing exit code on test failures.

Key terms

Finished this lesson?

Progress is saved in this browser. No account needed.

Type to search across SDETRole.

Full-text search by Pagefind · runs in your browser Open search page →