API Testing Tools: Clients, Mocking, Load Testing, and OpenAPI
API Testing Tools: Clients, Mocking, Load Testing, and OpenAPI
Testing APIs requires different tools at different stages: interactive clients for exploration, mocking tools for development, automated tests for CI, and load testing for production readiness. This guide covers the best options in each category and helps you pick the right tools for your workflow.
API Clients: Postman and Its Alternatives
Postman became the default API client, but it's increasingly bloated with team features, cloud sync requirements, and account gates. Several alternatives offer a better experience for individual developers and small teams.
Bruno
Bruno is an open-source API client that stores collections as files on your filesystem. No cloud sync, no accounts, no lock-in.
# Bruno collection structure (plain files in your repo)
api/
collection.bru
auth/
login.bru
refresh-token.bru
users/
get-user.bru
create-user.bru
Each .bru file is a plain text request definition:
meta {
name: Get User
type: http
seq: 1
}
get {
url: {{baseUrl}}/users/{{userId}}
body: none
auth: bearer {{token}}
}
tests {
test("status is 200", function() {
expect(res.status).to.equal(200);
});
}
Strengths: Git-friendly (collections are files), no cloud dependency, fast, open source. Environment variables, scripting, and test assertions are all supported.
Weaknesses: Smaller community than Postman. Fewer integrations. The scripting API is less mature.
Verdict: Bruno is the best choice for developers who want API collections versioned alongside their code.
Hoppscotch
Hoppscotch is a web-based API client (also available as a desktop app). It's fast, clean, and open source.
Strengths: Works in the browser (no install). WebSocket, SSE, GraphQL, and MQTT support. Real-time collaboration. Self-hostable.
Weaknesses: Web-based means it can't access localhost without the desktop app or browser extension. Collections aren't file-based by default.
Best for: Quick API exploration and teams that want a self-hosted alternative to Postman.
Insomnia
Insomnia is a desktop API client from Kong. It supports REST, GraphQL, gRPC, and WebSockets.
Strengths: Clean UI, good GraphQL support, plugin system, environment management.
Weaknesses: Kong has been pushing cloud features and account requirements. The open-source community forked it (Insomnium) in response to controversial changes. The product direction is uncertain.
Best for: GraphQL-heavy workflows where you need a GUI with schema exploration.
HTTPie (Desktop)
HTTPie also offers a desktop API client alongside its CLI tool. It has a modern, clean interface and supports collections, environments, and team sharing.
Comparison
| Tool | Open Source | File-based | Cloud-free | GraphQL | Price |
|---|---|---|---|---|---|
| Bruno | Yes | Yes | Yes | Plugin | Free |
| Hoppscotch | Yes | No | Self-host | Yes | Free |
| Insomnia | Partially | No | Partially | Yes | Freemium |
| Postman | No | No | No | Yes | Freemium |
Command-Line API Testing
curl
curl is everywhere. It's installed on virtually every Unix system and is the lingua franca for sharing API examples.
# GET request
curl https://api.example.com/users
# POST with JSON body
curl -X POST https://api.example.com/users \
-H "Content-Type: application/json" \
-d '{"name": "Alice", "email": "[email protected]"}'
# With authentication
curl -H "Authorization: Bearer $TOKEN" https://api.example.com/users
# Follow redirects, show response headers
curl -L -i https://api.example.com/users
# Save response to file
curl -o response.json https://api.example.com/users
# Verbose output (debug connection issues)
curl -v https://api.example.com/users
# Send form data
curl -X POST https://api.example.com/upload \
-F "[email protected]" \
-F "description=My file"
curl's syntax is cryptic but universal. Every API documentation includes curl examples. Learn it well.
HTTPie
HTTPie is curl for humans. It has intuitive syntax, colorized output, and JSON formatting by default.
# GET (default method)
http api.example.com/users
# POST with JSON (default content type)
http POST api.example.com/users name=Alice [email protected]
# Headers
http api.example.com/users Authorization:"Bearer $TOKEN"
# Query parameters
http api.example.com/users page==2 limit==10
# Form data
http --form POST api.example.com/upload [email protected]
# Download
http --download api.example.com/file.zip
# Sessions (persist cookies/auth)
http --session=myapi POST api.example.com/login username=admin password=secret
http --session=myapi api.example.com/dashboard
HTTPie's --session feature is particularly useful. It persists authentication across requests, so you can log in once and make authenticated requests without repeating headers.
Recommendation: Learn curl for universal compatibility and documentation. Use HTTPie for daily interactive use.
API Mocking
API mocking lets you develop against APIs that don't exist yet, test error scenarios, and avoid hitting rate limits on external services.
MSW (Mock Service Worker)
MSW intercepts HTTP requests at the network level, working with any HTTP client (fetch, axios, etc.) without changing your application code.
// handlers.ts
import { http, HttpResponse } from "msw";
export const handlers = [
http.get("/api/users", () => {
return HttpResponse.json([
{ id: 1, name: "Alice" },
{ id: 2, name: "Bob" },
]);
}),
http.post("/api/users", async ({ request }) => {
const body = await request.json();
return HttpResponse.json(
{ id: 3, ...body },
{ status: 201 }
);
}),
http.get("/api/users/:id", ({ params }) => {
if (params.id === "999") {
return HttpResponse.json(
{ error: "Not found" },
{ status: 404 }
);
}
return HttpResponse.json({ id: params.id, name: "Alice" });
}),
];
// In tests
import { setupServer } from "msw/node";
import { handlers } from "./handlers";
const server = setupServer(...handlers);
beforeAll(() => server.listen());
afterEach(() => server.resetHandlers());
afterAll(() => server.close());
MSW works in both Node.js (for tests) and the browser (for development). It's the best mocking tool for JavaScript/TypeScript applications because mocks live next to your code and use the same request/response APIs.
WireMock
WireMock is a Java-based HTTP mock server that runs as a standalone process. It's language-agnostic and supports complex scenarios.
# Run standalone
docker run -p 8080:8080 wiremock/wiremock
# Configure via API
curl -X POST http://localhost:8080/__admin/mappings \
-d '{
"request": { "method": "GET", "url": "/api/users" },
"response": { "status": 200, "jsonBody": [{"id": 1}] }
}'
Best for: Integration testing across multiple services, especially in polyglot environments.
Prism (OpenAPI Mock Server)
Prism generates a mock server directly from your OpenAPI specification:
npx @stoplight/prism-cli mock openapi.yaml
It validates requests against your spec and returns realistic responses. If your API is defined in OpenAPI, Prism is the fastest way to get a working mock.
Load Testing
k6
k6 is the best general-purpose load testing tool. Tests are written in JavaScript, execution is in Go (fast), and the output is detailed and actionable.
// load-test.js
import http from "k6/http";
import { check, sleep } from "k6";
export const options = {
stages: [
{ duration: "30s", target: 50 }, // ramp up to 50 users
{ duration: "1m", target: 50 }, // stay at 50
{ duration: "30s", target: 0 }, // ramp down
],
thresholds: {
http_req_duration: ["p(95)<500"], // 95th percentile under 500ms
http_req_failed: ["rate<0.01"], // less than 1% errors
},
};
export default function () {
const res = http.get("https://api.example.com/users");
check(res, {
"status is 200": (r) => r.status === 200,
"response time < 500ms": (r) => r.timings.duration < 500,
});
sleep(1);
}
k6 run load-test.js
k6's threshold system is particularly valuable. Define pass/fail criteria in the test itself, and k6 exits with a non-zero code if thresholds are breached. This makes load tests work in CI pipelines.
Vegeta
Vegeta is a command-line HTTP load testing tool written in Go. It's simpler than k6 and works well for quick benchmarks.
# Constant rate of 50 requests/second for 30 seconds
echo "GET https://api.example.com/users" | vegeta attack -rate=50/s -duration=30s | vegeta report
# Multiple endpoints
cat targets.txt | vegeta attack -rate=100/s -duration=1m | vegeta report
# Plot results
echo "GET https://api.example.com/users" | vegeta attack -rate=50/s -duration=30s | vegeta plot > results.html
hey
hey (formerly boom) is the simplest load testing tool. One command, immediate results.
# 1000 requests, 50 concurrent
hey -n 1000 -c 50 https://api.example.com/users
# With custom headers
hey -n 1000 -c 50 -H "Authorization: Bearer $TOKEN" https://api.example.com/users
Load Testing Comparison
| Tool | Language | Scripting | CI Integration | Complexity |
|---|---|---|---|---|
| k6 | Go (JS scripts) | Rich | Excellent | Moderate |
| Vegeta | Go | Minimal | Good | Low |
| hey | Go | None | Basic | Minimal |
Use k6 for production load testing with complex scenarios. Use hey for quick "is this endpoint fast?" checks. Use Vegeta when you need something between the two.
OpenAPI/Swagger Tools
Specification Authoring
Write OpenAPI specs in YAML. Use a linter to catch errors:
# Redocly CLI (lint + bundle + preview)
npx @redocly/cli lint openapi.yaml
npx @redocly/cli preview-docs openapi.yaml
# Spectral (customizable linting rules)
npx @stoplight/spectral-cli lint openapi.yaml
Code Generation
Generate client libraries and server stubs from your OpenAPI spec:
# TypeScript client
npx openapi-typescript openapi.yaml -o src/api-types.ts
# Full SDK generation
npx @openapitools/openapi-generator-cli generate \
-i openapi.yaml -g typescript-fetch -o src/api-client
openapi-typescript generates TypeScript types from your spec without runtime code. It's lightweight and integrates well with fetch-based clients.
Documentation
# Redoc (static docs)
npx @redocly/cli build-docs openapi.yaml -o docs/api.html
# Swagger UI (interactive)
docker run -p 8080:8080 -e SWAGGER_JSON=/spec/openapi.yaml \
-v ./openapi.yaml:/spec/openapi.yaml swaggerapi/swagger-ui
Recommendations
- API client: Use Bruno for collections versioned in git. Use HTTPie for daily command-line exploration.
- Mocking: Use MSW for JavaScript/TypeScript projects (browser and Node.js). Use Prism if you have an OpenAPI spec.
- Load testing: Use k6 for serious load testing with CI integration. Use hey for quick one-off benchmarks.
- OpenAPI: Lint your specs with Redocly CLI. Generate TypeScript types with openapi-typescript. Don't generate full SDKs unless your API is stable.
- General principle: Version your API test collections, mock configurations, and load test scripts alongside your code. API testing artifacts are code, not throwaway files.