DEV Community

API Testing in Practice: Automating Postman Collections with Newman

Why API testing needs its own layer

Unit tests check a function in isolation. End-to-end UI tests check the whole product through a browser. Neither of those is great at answering the question a backend team actually cares about day to day: "did this endpoint just break its contract?" — wrong status code, missing field, broken auth, a response shape that changed without anyone updating the docs. That's the gap API testing frameworks fill, and it's exactly the space covered in "Top 10 API Testing Tools" on Medium, which lists tools like Postman, SoapUI, and Rest-Assured side by side.

This article focuses on one of the most widely adopted combinations in that space: Postman for designing and exploring requests, and Newman, Postman's official CLI runner, for turning that collection into something a pipeline can execute automatically.

What Newman actually is

Newman is the command-line collection runner published and maintained by the Postman team. It takes a Postman collection — a JSON file with requests, assertions written in JavaScript, and environment variables — and runs it outside the Postman desktop app, which is what makes it usable in CI/CD.

Official repository: https://github.com/postmanlabs/newman

Target API for this example

Instead of a private backend, I used httpbin.org, a small, public HTTP testing service that echoes back whatever you send it — ideal for demonstrating real requests without needing any authentication or infrastructure of my own.

Step 1: writing assertions inside a Postman request

Inside the Postman app, each request has a "Tests" tab where you write plain JavaScript assertions that run against the response:

// Tests tab for GET https://httpbin.org/get?user=marymar

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

pm.test("Response contains the query param sent", () => {
    const body = pm.response.json();
    pm.expect(body.args.user).to.eql("marymar");
});

pm.test("Response time is under 800ms", () => {
    pm.expect(pm.response.responseTime).to.be.below(800);
});
Enter fullscreen mode Exit fullscreen mode

For a POST request that sends a JSON body:

// Tests tab for POST https://httpbin.org/post

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

pm.test("Echoed body matches what was sent", () => {
    const body = pm.response.json();
    pm.expect(body.json.title).to.eql(pm.collectionVariables.get("title"));
});
Enter fullscreen mode Exit fullscreen mode

Step 2: exporting the collection

From Postman: collection menu → Export → Collection v2.1 → save as api-tests.postman_collection.json. That file is what Newman consumes — it's plain JSON, so it belongs in version control right next to the application code it's testing.

Step 3: running it with Newman

npm install -g newman

newman run api-tests.postman_collection.json \
  --env-var baseUrl="https://httpbin.org" \
  --reporters cli,html \
  --reporter-html-export reports/report.html
Enter fullscreen mode Exit fullscreen mode

Newman prints a summary per request — assertions passed, failed, response time — and, with the html reporter, generates a shareable report that doesn't require anyone to have Postman installed to read it.

Step 4: wiring it into GitHub Actions

# .github/workflows/api-tests.yml
name: API Tests

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

jobs:
  newman:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout repository
        uses: actions/checkout@v4

      - name: Set up Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '20'

      - name: Install Newman
        run: npm install -g newman newman-reporter-html

      - name: Run API tests
        run: |
          newman run api-tests.postman_collection.json \
            --env-var baseUrl="https://httpbin.org" \
            --reporters cli,html,junit \
            --reporter-html-export reports/report.html \
            --reporter-junit-export reports/report.xml

      - name: Upload test report
        if: always()
        uses: actions/upload-artifact@v4
        with:
          name: newman-report
          path: reports/
Enter fullscreen mode Exit fullscreen mode

The --reporters cli,html,junit line matters: cli for readable logs in the Actions output, html for a report a non-technical teammate can open, and junit because most CI dashboards (GitHub, GitLab, Jenkins) know how to render JUnit XML as a native test summary with pass/fail counts.

A more complete real-world example

For a fuller example that organizes tests by feature (auth, user profile, and a resource-tracking flow) instead of one flat collection, this project walks through the same Postman + Newman + GitHub Actions pattern end to end:

https://github.com/postmanlabs/newman (the runner itself — clone it to see its own test suite structure as a reference for organizing a Newman-driven project)

Where this approach falls short

Newman runs exactly what's in the exported JSON — it has no concept of the API's actual behavior beyond the assertions someone wrote by hand. It won't generate edge cases for you, and a collection that's gone stale (testing an old response shape) will happily keep "passing" against a mismatched mental model of the API. It's a contract-verification tool, not a fuzzer or a schema-validation engine — for deeper coverage, pairing it with a schema validation step (e.g. validating the response against an OpenAPI spec) closes that gap.

Conclusion

Postman gets teams to write and explore requests quickly; Newman is what turns that exploration into a repeatable, CI-friendly safety net. The combination doesn't require learning a new language or framework — if a team can already write a Postman request, they can write a Postman test, and from there the jump to "this runs on every pull request" is just a few lines of YAML.

Top comments (0)