API testing strategies with Postman/Newman MCP integration, contract testing, unit and integration tests, and framework-specific examples. Use when setting up API testing infrastructure, integrating Postman MCP, writing test suites, or automating API testing in CI/CD pipelines.
Automates API testing with pytest, Jest, Postman MCP, and Newman for CI/CD pipelines.
npx claudepluginhub karchtho/my-claude-marketplaceThis skill inherits all available tools. When active, it can use any tool Claude has access to.
Master API testing strategies and automation to ensure your APIs are reliable, secure, and performant.
Structure your tests from bottom to top:
E2E Tests (Postman collections, full flow)
/ \
/ \
Integration Tests API Tests
(DB, caching, etc.) (endpoint behavior)
/ \
/ \
Unit Tests (business logic, validation)
Test individual functions and business logic in isolation:
# pytest example
import pytest
from app.models import User
def test_user_password_hashing():
user = User(email="test@example.com")
user.set_password("mypassword")
assert user.password != "mypassword"
assert user.check_password("mypassword") is True
assert user.check_password("wrongpassword") is False
def test_user_email_validation():
with pytest.raises(ValueError):
User(email="invalid-email")
Test API endpoints with real dependencies (database, caching):
# pytest example with fixtures
import pytest
from fastapi.testclient import TestClient
from app import app
from app.db import get_db
@pytest.fixture
def client(db_session):
def override_get_db():
return db_session
app.dependency_overrides[get_db] = override_get_db
return TestClient(app)
def test_create_user(client):
response = client.post("/api/users", json={
"email": "newuser@example.com",
"name": "New User"
})
assert response.status_code == 201
assert response.json()["email"] == "newuser@example.com"
def test_get_user(client, db_session):
# Create test user
from app.models import User
user = User(email="test@example.com", name="Test")
db_session.add(user)
db_session.commit()
response = client.get(f"/api/users/{user.id}")
assert response.status_code == 200
assert response.json()["name"] == "Test"
Test full user workflows using Postman collections:
import pytest
from fastapi.testclient import TestClient
from app import app
client = TestClient(app)
class TestUserAPI:
def test_list_users(self):
response = client.get("/api/users")
assert response.status_code == 200
assert isinstance(response.json(), list)
def test_create_user_success(self):
response = client.post("/api/users", json={
"email": "newuser@example.com",
"name": "New User",
"password": "secure123"
})
assert response.status_code == 201
data = response.json()
assert data["email"] == "newuser@example.com"
assert "password" not in data # Never expose passwords
def test_create_user_validation_error(self):
response = client.post("/api/users", json={
"email": "invalid-email",
"name": "Test"
})
assert response.status_code == 422
assert "email" in response.json()["detail"][0]["loc"]
def test_get_nonexistent_user(self):
response = client.get("/api/users/99999")
assert response.status_code == 404
def test_update_user(self):
# Create user first
create_resp = client.post("/api/users", json={
"email": "user@example.com",
"name": "Original"
})
user_id = create_resp.json()["id"]
# Update user
response = client.patch(f"/api/users/{user_id}", json={
"name": "Updated"
})
assert response.status_code == 200
assert response.json()["name"] == "Updated"
def test_delete_user(self):
# Create user
create_resp = client.post("/api/users", json={
"email": "deleteme@example.com",
"name": "Delete Me"
})
user_id = create_resp.json()["id"]
# Delete
response = client.delete(f"/api/users/{user_id}")
assert response.status_code == 204
# Verify deletion
get_resp = client.get(f"/api/users/{user_id}")
assert get_resp.status_code == 404
import pytest
@pytest.mark.parametrize("status_code,expected_status", [
(200, 200),
(201, 201),
(400, 400),
(404, 404),
])
def test_status_codes(status_code, expected_status):
response = client.get(f"/api/test?status={status_code}")
assert response.status_code == expected_status
import { gql } from '@apollo/client';
import { ApolloClient } from '@apollo/client';
const client = new ApolloClient({
uri: 'http://localhost:4000/graphql'
});
describe('GraphQL User Queries', () => {
it('should fetch user by ID', async () => {
const query = gql`
query GetUser($id: ID!) {
user(id: $id) {
id
name
email
}
}
`;
const result = await client.query({
query,
variables: { id: '1' }
});
expect(result.data.user).toBeDefined();
expect(result.data.user.email).toBe('user@example.com');
});
it('should list users with pagination', async () => {
const query = gql`
query ListUsers($first: Int!) {
users(first: $first) {
edges {
node {
id
name
}
}
pageInfo {
hasNextPage
}
}
}
`;
const result = await client.query({
query,
variables: { first: 10 }
});
expect(result.data.users.edges).toHaveLength(10);
expect(result.data.users.pageInfo.hasNextPage).toBeDefined();
});
});
describe('GraphQL Mutations', () => {
it('should create user with validation', async () => {
const mutation = gql`
mutation CreateUser($input: CreateUserInput!) {
createUser(input: $input) {
user {
id
email
}
errors {
field
message
}
success
}
}
`;
const result = await client.mutate({
mutation,
variables: {
input: { email: 'newuser@example.com', name: 'New' }
}
});
expect(result.data.createUser.success).toBe(true);
expect(result.data.createUser.user.id).toBeDefined();
expect(result.data.createUser.errors).toHaveLength(0);
});
it('should return errors for invalid input', async () => {
const mutation = gql`
mutation CreateUser($input: CreateUserInput!) {
createUser(input: $input) {
user {
id
}
errors {
field
message
}
success
}
}
`;
const result = await client.mutate({
mutation,
variables: {
input: { email: 'invalid-email', name: '' }
}
});
expect(result.data.createUser.success).toBe(false);
expect(result.data.createUser.errors.length).toBeGreaterThan(0);
});
});
The Postman MCP server enables running Postman collections through Claude Code:
Method 1: Via Smithery (Recommended)
npx -y @smithery/cli install mcp-postman --client claude
Method 2: Manual Installation
git clone https://github.com/shannonlal/mcp-postman.git
cd mcp-postman
pnpm install
pnpm build
Add to ~/.config/claude/config.json:
{
"mcpServers": {
"postman": {
"command": "node",
"args": ["/path/to/mcp-postman/build/index.js"],
"env": {
"POSTMAN_API_KEY": "your-api-key-here"
}
}
}
}
Once configured, interact with Postman from Claude Code:
User: "Run the user-api collection from my Postman workspace"
Claude can now:
- List available collections
- Execute collections with specific environments
- Parse test results
- Run in CI/CD pipelines
- Generate test reports
Newman CLI runs Postman collections headlessly for CI/CD:
npm install -g newman
npm install -g newman-reporter-html
# Basic execution
newman run collection.json
# With environment
newman run collection.json -e environment.json
# With variables
newman run collection.json --global-var "base_url=https://api.example.com"
# Multiple reporters
newman run collection.json \
--reporters cli,json,html \
--reporter-json-export results.json \
--reporter-html-export report.html
GitHub Actions:
name: API Tests
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v2
- run: npm install -g newman
- run: |
newman run postman/collection.json \
-e postman/environment.json \
--reporters cli,json \
--reporter-json-export results.json
Validate APIs match their specifications:
# Using openapi-spec-validator
from openapi_spec_validator import validate_spec
import json
with open('openapi.json') as f:
spec = json.load(f)
try:
validate_spec(spec)
print("OpenAPI spec is valid")
except Exception as e:
print(f"Spec validation error: {e}")
// Consumer test with Pact
import { Pact } from '@pact-foundation/pact';
describe('User Consumer', () => {
const provider = new Pact({
consumer: 'UserApp',
provider: 'UserAPI'
});
it('should fetch user', () => {
return provider
.addInteraction({
state: 'user exists',
uponReceiving: 'a request for user 123',
withRequest: { method: 'GET', path: '/api/users/123' },
willRespondWith: {
status: 200,
body: { id: 123, name: 'John', email: 'john@example.com' }
}
})
.then(() => fetch('http://localhost:8080/api/users/123'))
.then(response => response.json())
.then(data => expect(data.email).toBe('john@example.com'));
});
});
def test_bearer_token_required(client):
response = client.get("/api/users")
assert response.status_code == 401
def test_valid_bearer_token(client):
headers = {"Authorization": "Bearer valid-token-123"}
response = client.get("/api/users", headers=headers)
assert response.status_code == 200
def test_invalid_bearer_token(client):
headers = {"Authorization": "Bearer invalid-token"}
response = client.get("/api/users", headers=headers)
assert response.status_code == 401
def test_api_key_validation(client):
headers = {"X-API-Key": "valid-key"}
response = client.get("/api/users", headers=headers)
assert response.status_code == 200
headers = {"X-API-Key": "invalid-key"}
response = client.get("/api/users", headers=headers)
assert response.status_code == 401
# Load testing with locust
from locust import HttpUser, task, constant
class APIUser(HttpUser):
wait_time = constant(1)
@task
def list_users(self):
self.client.get("/api/users")
@task
def get_user(self):
self.client.get("/api/users/1")
Run with: locust -f locustfile.py -u 100 -r 10
For detailed guidance, see:
references/postman-mcp-setup.md - Complete Postman MCP setupreferences/testing-strategies.md - Testing patterns and organizationreferences/contract-testing.md - OpenAPI and Pact testing