Home / Developer Tools / API Testing Complete Guide: Postman, REST Client, and Automated Testing
Developer Tools

API Testing Complete Guide: Postman, REST Client, and Automated Testing

Master API testing with Postman collections, VS Code REST Client, automated testing with Newman and Jest, contract testing, and CI/CD integration for compreh...

What you will learn

Practical execution with concise explanations, real implementation patterns, and production-ready recommendations.

"header": [], "url": { "raw": "https://api.contoso.com/v1/users", "protocol": "https", "host": ["api", "contoso", "com"], "path": ["v1", "users"] }``` } }


**Request with Authentication:**

```javascript
// Collection Variables
baseUrl = https://api.contoso.com
apiKey = your-api-key-here

// Request Header
{
  "Authorization": "Bearer {{accessToken}}",
  "X-API-Key": "{{apiKey}}"
}

// Pre-request Script (OAuth2 token)
pm.sendRequest({
```yaml
url: 'https://auth.contoso.com/oauth/token',
method: 'POST',
header: {
    'Content-Type': 'application/x-www-form-urlencoded'
},
body: {
    mode: 'urlencoded',
    urlencoded: [
        {key: 'grant_type', value: 'client_credentials'},
        {key: 'client_id', value: pm.environment.get('clientId')},
        {key: 'client_secret', value: pm.environment.get('clientSecret')}
    ]
}```
}, function (err, response) {
```text
if (response.code === 200) {
    pm.environment.set('accessToken', response.json().access_token);
}```
});

Test Scripts

Response Validation:

// Status code validation
pm.test("Status code is 200", function () {
```text
pm.response.to.have.status(200);```
});

// Response time check
pm.test("Response time is less than 500ms", function () {
```text
pm.expect(pm.response.responseTime).to.be.below(500);```
});

// JSON schema validation
const schema = {
```text
"type": "object",
"properties": {
    "userId": { "type": "number" },
    "username": { "type": "string" },
    "email": { "type": "string", "format": "email" }
},
"required": ["userId", "username"]```
};

pm.test("Schema is valid", function () {
```text
pm.response.to.have.jsonSchema(schema);```
});

// Body content validation
pm.test("User has correct email domain", function () {
```javascript
const jsonData = pm.response.json();
pm.expect(jsonData.email).to.include('@contoso.com');```
});

Data-Driven Testing:

# users.csv
userId,expectedName,expectedEmail
1,John Doe,john@contoso.com
2,Jane Smith,jane@contoso.com

Architecture Overview: Collection Runner → Select CSV file

const jsonData = pm.response.json(); pm.expect(jsonData.name).to.eql(pm.iterationData.get("expectedName")); pm.expect(jsonData.email).to.eql(pm.iterationData.get("expectedEmail"));``` });


## Environment Management

**Development Environment:**





```json
{
  "name": "Development",
  "values": [
```json
{
  "key": "baseUrl",
  "value": "https://api-dev.contoso.com",
  "enabled": true
},
{
  "key": "clientId",
  "value": "dev-client-id",
  "enabled": true
}```
  ]
}

Switch Environments via CLI:

newman run collection.json \
  -e dev-environment.json \
  --reporters cli,json \
  --reporter-json-export results.json

VS Code REST Client

VS Code REST Client

Figure: Windows Terminal – split panes with PowerShell, Bash, and CLI sessions.

Basic Requests

.http File:

### Get all users
GET https://api.contoso.com/v1/users HTTP/1.1
Authorization: Bearer {{$dotenv TOKEN}}

### Create user
POST https://api.contoso.com/v1/users HTTP/1.1
Content-Type: application/json
Authorization: Bearer {{$dotenv TOKEN}}

{
  "username": "johndoe",
  "email": "john@contoso.com",
  "role": "user"
}

### Update user
PUT https://api.contoso.com/v1/users/123 HTTP/1.1
Content-Type: application/json
Authorization: Bearer {{$dotenv TOKEN}}

{
  "email": "john.doe@contoso.com"
}

### Delete user
DELETE https://api.contoso.com/v1/users/123 HTTP/1.1
Authorization: Bearer {{$dotenv TOKEN}}

Variables and Environment

Environment Variables (.env):

TOKEN=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
BASE_URL=https://api.contoso.com
API_VERSION=v1

Request Variables:

@baseUrl = {{$dotenv BASE_URL}}
@apiVersion = {{$dotenv API_VERSION}}
@contentType = application/json

### Get user by ID
@userId = 123

GET {{baseUrl}}/{{apiVersion}}/users/{{userId}} HTTP/1.1
Content-Type: {{contentType}}
Authorization: Bearer {{$dotenv TOKEN}}

### Use response in next request
@authToken = {{login.response.body.$.token}}

POST {{baseUrl}}/{{apiVersion}}/protected HTTP/1.1
Authorization: Bearer {{authToken}}

Pre-Request Scripts

Dynamic Headers:

## @name login
POST https://api.contoso.com/v1/auth/login HTTP/1.1
Content-Type: application/json





{
  "username": "admin",
  "password": "password123"
}

###
## Extract token from login response
@token = {{login.response.body.$.accessToken}}





GET https://api.contoso.com/v1/users HTTP/1.1
Authorization: Bearer {{token}}

Automated Testing with Newman

CLI Execution

Basic Run:

## Install Newman
npm install -g newman

## Run collection
newman run collection.json \
  --environment production.json \
  --reporters cli,htmlextra \
  --reporter-htmlextra-export report.html

Expected output:

added 245 packages in 8s
found 0 vulnerabilities

Terminal output for npm install

Advanced Options:

newman run collection.json \
  --environment prod.json \
  --globals globals.json \
  --iteration-count 10 \
  --delay-request 1000 \
  --timeout-request 30000 \
  --bail \
  --reporters cli,json,junit \
  --reporter-junit-export results.xml

CI/CD Integration

GitHub Actions:

name: API Tests

on:
  push:
```yaml
branches: [main]```
  schedule:
```text
- cron: '0 */6 * * *'  # Every 6 hours

jobs: api-tests:

runs-on: ubuntu-latest
steps:
  - uses: actions/checkout@v4
  
  - name: Setup Node.js
    uses: actions/setup-node@v4
    with:
      node-version: '20'
  
  - name: Install Newman
    run: npm install -g newman newman-reporter-htmlextra
  
  - name: Run API Tests


    run: |
      newman run postman/collection.json \
        --environment postman/prod.json \
        --reporters cli,htmlextra,junit \
        --reporter-htmlextra-export test-results/report.html \
        --reporter-junit-export test-results/results.xml
    env:
      API_KEY: ${{ secrets.API_KEY }}
  
  - name: Publish Test Results
    uses: dorny/test-reporter@v1
    if: always()
    with:
      name: API Test Results
      path: test-results/results.xml
      reporter: jest-junit
  
  - name: Upload HTML Report
    uses: actions/upload-artifact@v3
    if: always()
    with:
      name: test-report
      path: test-results/report.html

**Azure DevOps Pipeline:**

```yaml
trigger:
  branches:
```yaml
include:
  - main

pool: vmImage: 'ubuntu-latest'

steps:

  • task: NodeTool@0
inputs:
  versionSpec: '20.x'
  • script: npm install -g newman newman-reporter-htmlextra
displayName: 'Install Newman'
  • script: | newman run $(System.DefaultWorkingDirectory)/postman/collection.json
    --environment $(System.DefaultWorkingDirectory)/postman/prod.json
    --reporters cli,htmlextra,junit
    --reporter-htmlextra-export $(Build.ArtifactStagingDirectory)/report.html
    --reporter-junit-export $(Build.ArtifactStagingDirectory)/results.xml
displayName: 'Run API Tests'
env:
  API_KEY: $(API_KEY)
  • task: PublishTestResults@2
inputs:
  testResultsFormat: 'JUnit'
  testResultsFiles: '$(Build.ArtifactStagingDirectory)/results.xml'
condition: always()
  • task: PublishBuildArtifacts@1
inputs:
  pathToPublish: '$(Build.ArtifactStagingDirectory)'
  artifactName: 'api-test-results'
condition: always()

## Unit Testing APIs with Jest

### Setup





```bash
npm install --save-dev jest supertest @types/jest

Expected output:

added 245 packages in 8s
found 0 vulnerabilities

Terminal output for npm install

jest.config.js:

module.exports = {
  testEnvironment: 'node',
  coveragePathIgnorePatterns: ['/node_modules/'],
  testMatch: ['**/__tests__/**/*.test.js'],
  collectCoverageFrom: ['src/**/*.js'],
  coverageThreshold: {
```yaml
global: {
  branches: 80,
  functions: 80,
  lines: 80,
  statements: 80
}```
  }
};

Testing Express API

__tests__/users.test.js:

const request = require('supertest');
const app = require('../src/app');
const { setupDatabase, clearDatabase } = require('./fixtures/db');

beforeAll(async () => {
  await setupDatabase();
});

afterAll(async () => {
  await clearDatabase();
});

describe('Users API', () => {
  describe('GET /api/users', () => {
```javascript
test('should return all users', async () => {
  const response = await request(app)
    .get('/api/users')
    .set('Authorization', 'Bearer valid-token')
    .expect(200);
  
  expect(response.body).toHaveLength(3);
  expect(response.body[0]).toHaveProperty('id');
  expect(response.body[0]).toHaveProperty('username');
});

test('should return 401 without authentication', async () => {
  await request(app)
    .get('/api/users')
    .expect(401);
});```
  });
  
  describe('POST /api/users', () => {
```javascript
test('should create new user', async () => {
  const newUser = {
    username: 'testuser',
    email: 'test@contoso.com',
    password: 'SecurePass123!'
  };
  
  const response = await request(app)
    .post('/api/users')
    .send(newUser)
    .expect(201);
  
  expect(response.body).toHaveProperty('id');
  expect(response.body.username).toBe(newUser.username);
  expect(response.body).not.toHaveProperty('password');
});

test('should validate required fields', async () => {
  const response = await request(app)
    .post('/api/users')
    .send({ username: 'testuser' })
    .expect(400);
  
  expect(response.body.errors).toContainEqual(
    expect.objectContaining({ field: 'email' })
  );
});```
  });
  
  describe('PUT /api/users/:id', () => {
```javascript
test('should update existing user', async () => {
  const response = await request(app)
    .put('/api/users/1')
    .set('Authorization', 'Bearer valid-token')
    .send({ email: 'updated@contoso.com' })
    .expect(200);
  
  expect(response.body.email).toBe('updated@contoso.com');
});```
  });
  
  describe('DELETE /api/users/:id', () => {
```javascript
test('should delete user', async () => {
  await request(app)
    .delete('/api/users/1')
    .set('Authorization', 'Bearer admin-token')
    .expect(204);
  
  // Verify deletion
  await request(app)
    .get('/api/users/1')
    .expect(404);
});```
  });
});

Mocking External APIs

__tests__/orders.test.js:

const nock = require('nock');
const request = require('supertest');
const app = require('../src/app');

describe('Orders API with External Dependencies', () => {
  afterEach(() => {
```text
nock.cleanAll();```
  });
  
  test('should process order with payment gateway', async () => {
```javascript
// Mock payment gateway API
nock('https://payment-gateway.contoso.com')
  .post('/api/v1/charge')
  .reply(200, { transactionId: 'txn_123', status: 'success' });

// Mock inventory service
nock('https://inventory-service.contoso.com')
  .post('/api/v1/reserve')
  .reply(200, { reserved: true });

const order = {
  customerId: 1,
  items: [{ productId: 101, quantity: 2 }],
  paymentMethod: 'credit_card'
};

const response = await request(app)
  .post('/api/orders')
  .send(order)
  .expect(201);

expect(response.body.status).toBe('confirmed');
expect(response.body.transactionId).toBe('txn_123');```
  });
});

Contract Testing with Pact

Provider Contract

__tests__/pact/provider.test.js:

const { Verifier } = require('@pact-foundation/pact');
const app = require('../src/app');

describe('Pact Verification', () => {
  test('validates provider against consumer contracts', async () => {
```javascript
const server = app.listen(3000);

try {
  await new Verifier({
    provider: 'UserService',
    providerBaseUrl: 'http://localhost:3000',
    pactUrls: [
      'https://pact-broker.contoso.com/pacts/provider/UserService/consumer/WebApp/latest'
    ],
    publishVerificationResult: true,
    providerVersion: process.env.GIT_COMMIT,
    stateHandlers: {
      'user with ID 1 exists': async () => {
        // Setup test data
        await createTestUser({ id: 1, username: 'johndoe' });
      }
    }
  }).verifyProvider();
} finally {
  server.close();
}```
  });
});

Consumer Contract

__tests__/pact/consumer.test.js:

const { PactV3, MatchersV3 } = require('@pact-foundation/pact');
const { getUserById } = require('../src/userClient');

const provider = new PactV3({
  consumer: 'WebApp',
  provider: 'UserService'
});

describe('User Client', () => {
  test('retrieves user by ID', async () => {
```javascript
await provider
  .given('user with ID 1 exists')
  .uponReceiving('a request for user 1')
  .withRequest({
    method: 'GET',
    path: '/api/users/1',
    headers: { 'Accept': 'application/json' }
  })
  .willRespondWith({
    status: 200,
    headers: { 'Content-Type': 'application/json' },
    body: {
      id: MatchersV3.integer(1),
      username: MatchersV3.string('johndoe'),
      email: MatchersV3.regex('.*@.*', 'john@contoso.com')
    }
  })
  .executeTest(async (mockServer) => {
    const user = await getUserById(1, mockServer.url);
    expect(user.id).toBe(1);
    expect(user.username).toBe('johndoe');
  });```
  });
});

Performance Testing

Apache Bench (ab)

## Basic load test
ab -n 1000 -c 10 https://api.contoso.com/v1/users





## With authentication
ab -n 1000 -c 10 \
  -H "Authorization: Bearer token123" \
  https://api.contoso.com/v1/users





## POST request
ab -n 1000 -c 10 \
  -T application/json \
  -p payload.json \
  https://api.contoso.com/v1/users





k6 Load Testing

load-test.js:

import http from 'k6/http';
import { check, sleep } from 'k6';

export const options = {
  stages: [
```json
{ duration: '30s', target: 20 },  // Ramp up
{ duration: '1m', target: 20 },   // Stay at 20 users
{ duration: '30s', target: 0 }    // Ramp down```
  ],
  thresholds: {
```yaml
http_req_duration: ['p(95)<500'],  // 95% requests < 500ms
http_req_failed: ['rate<0.01']      // Error rate < 1%```
  }
};

export default function () {
  const response = http.get('https://api.contoso.com/v1/users', {
```yaml
headers: { 'Authorization': 'Bearer token123' }```
  });
  
  check(response, {
```javascript
'status is 200': (r) => r.status === 200,
'response time < 500ms': (r) => r.timings.duration < 500```
  });
  
  sleep(1);
}

Run Load Test:

k6 run load-test.js

## Cloud execution
k6 cloud load-test.js





Best Practices

  1. Test Pyramid: More unit tests, fewer integration tests, minimal E2E tests
  2. Idempotency: Ensure tests can run multiple times without side effects
  3. Test Data Isolation: Each test creates/cleans up its own data
  4. Mock External Services: Use nock or similar to avoid dependencies
  5. Environment Variables: Never hardcode credentials in collections
  6. Version Control: Commit Postman collections and .http files
  7. Continuous Validation: Run API tests on every deployment
  8. Contract Testing: Validate API compatibility between services

Troubleshooting

Postman SSL Errors:

Architecture Overview: ## Disable SSL verification (development only)

Newman Timeout Issues:

## Increase timeout
newman run collection.json --timeout-request 60000

Jest Watch Mode:

## Run tests in watch mode
npm test -- --watch





Expected output:

Test Files  3 passed (3)
      Tests  26 passed (26)
   Duration  1.23s

Terminal output for npm test

Architecture Decision and Tradeoffs

When designing development workflow solutions with Developer Tools, consider these key architectural trade-offs:

Approach Best For Tradeoff
Managed / platform service Rapid delivery, reduced ops burden Less customisation, potential vendor lock-in
Custom / self-hosted Full control, advanced tuning Higher operational overhead and cost

Recommendation: Start with the managed approach for most workloads and move to custom only when specific requirements demand it.

Validation and Versioning

  • Last validated: April 2026
  • Validate examples against your tenant, region, and SKU constraints before production rollout.
  • Keep module, CLI, and SDK versions pinned in automation pipelines and review quarterly.

Security and Governance Considerations

  • Apply least-privilege access using RBAC roles and just-in-time elevation for admin tasks.
  • Store secrets in managed secret stores and avoid embedding credentials in scripts or source files.
  • Enable audit logging, data protection policies, and periodic access reviews for regulated workloads.

Cost and Performance Notes

  • Define budgets and alerts, then monitor usage and cost trends continuously after go-live.
  • Baseline performance with synthetic and real-user checks before and after major changes.
  • Scale resources with measured thresholds and revisit sizing after usage pattern changes.

Official Microsoft References

  • https://learn.microsoft.com/visualstudio/
  • https://learn.microsoft.com/azure/devops/
  • https://learn.microsoft.com/github/

Public Examples from Official Sources

  • These examples are sourced from official public Microsoft documentation and sample repositories.
  • Documentation examples: https://learn.microsoft.com/visualstudio/
  • Sample repositories: https://github.com/microsoft/vscode-extension-samples
  • Prefer adapting these examples to your tenant, subscriptions, and governance requirements before production use.

Key Takeaways

  • Postman provides comprehensive manual testing with scripting capabilities
  • VS Code REST Client offers lightweight, version-controlled API testing
  • Newman enables automated API testing in CI/CD pipelines
  • Jest with supertest validates API behavior at the unit level
  • Contract testing with Pact ensures API compatibility across services

Next Steps

  • Explore GraphQL testing with Postman and GraphQL Playground
  • Implement API monitoring with Postman Monitors or Datadog Synthetics
  • Learn gRPC testing with grpcurl and Postman
  • Investigate chaos engineering for API resilience testing

Additional Resources


Test early, test often, automate everything.

Discussion