Guide
API Testing Examples & Tutorial
Table of Contents
Modern distributed applications rely on various APIs, including REST, GraphQL, gRPC, and WebSocket. Each requires an appropriate testing strategy in isolation and as part of an integrated web of interdependent systems.
This article provides real-world examples demonstrating how to effectively test each API in practical scenarios, such as an e-commerce checkout or a real-time chat application. It covers step-by-step testing workflows, including chaining requests, verifying response data, handling failures, and utilizing logs and traces to troubleshoot issues across software services. The API testing examples in this article draw on realistic tools and techniques to reflect how modern systems behave in production.
Summary of API types & testing approaches
API type | Testing approach |
---|---|
REST |
|
GraphQL |
|
gRPC |
|
WebSocket |
|
REST API testing examples
Testing a REST API often involves chaining multiple calls to simulate a real user's journey. In this example, a user logs in, views their cart, and places an order. Each step passes data, such as tokens or cart IDs, to the next and must be verified both individually and as part of a comprehensive workflow.
Scenario setup
The API exposes these endpoints:
- POST /api/login authenticates the user and returns a JSON token
- GET /api/cart fetches the user’s cart, using the auth token
- POST /api/checkout attempts to place the order, using cart data and auth
Test workflow
Step 1 – Login
Send a valid login request and check the response:
POST /api/login HTTP/1.1
Content-Type: application/json
{ "username": "alice", "password": "secret123" }
Expected result:
{ "token": "exampleToken123" }
Assert that the token exists and has the expected format, such as a valid JWT. This token will be reused in the next steps.
Step 2 – Get cart
Use the token in an authorization header to request the cart:
GET /api/cart HTTP/1.1
Authorization: Bearer exampleToken123
Expected result:
{ "items": [
{ "productId": 42, "quantity": 2 },
{ "productId": 17, "quantity": 1 }
]
}
Verify that:
- The response structure matches the schema
- The items belong to the correct user
- Item counts and types are correct
Step 3 – Checkout
POST /api/checkout HTTP/1.1
Authorization: Bearer exampleToken123
Content-Type: application/json
{ "paymentMethod": "credit_card", "addressId": 99 }
Expected result:
{ "orderId": 12345, "status": "confirmed" }
Validate:
orderId
is a valid IDstatus
indicates success- No missing or inconsistent fields from previous steps
Also test invalid tokens, out-of-stock products, and invalid payment methods or addresses.
Chaining, validation, and token propagation
Instead of treating these steps in isolation, test the entire user flow as a chain:
- Ensure the cart retrieved corresponds to the user logged in.
- Verify that the order placed includes items from the cart.
- Propagate the token across steps and confirm system consistency.
You should also verify correlation across backend logs. For example, attach an X-Request-ID
or trace-id
header to each request and check that it's preserved end-to-end in logs and traces. This helps detect errors that surface only during multi-step flows.
Automating the flow
Instead of using isolated scripts or manually chained API client calls (which can be brittle and siloed), use a tool like Multiplayer notebooks that supports reusable variables, conditional logic, and replayable test flows.
For the testing scenario above, notebooks would allow you to:
- Store the login token as a variable
- Use it in later requests automatically
- Add inline assertions
- Document decisions and capture backend trace links
This improves reproducibility and reduces context switching by combining executable tests with live documentation.
Full-stack session recording
Learn moreFinal validation
Along with checking HTTP status codes, your tests should validate:
- The cart contains the expected items and quantities
- Checkout returns a valid
orderId
and status - Backend services emit proper logs and metrics tied to the request ID
Cleaning up test data after each run (especially in shared environments) helps ensure that repeated tests remain isolated and deterministic.
GraphQL API testing examples
GraphQL APIs let clients query only the data they need, which simplifies frontend integration but increases backend testing complexity. Each query may touch multiple resolvers, services, and data layers. Testing needs to validate not only the structure but also the backend behavior, schema consistency, and traceability across systems.
Scenario
A social app exposes a GraphQL API for querying user profiles and posts. The query:
query GetProfile($userId: ID!) {
user(id: $userId) {
name
bio
recentPosts(limit: 3) {
title
timestamp
}
}
}
This fetches a user’s name, bio, and their latest three post titles.
Your test should:
- Assert that all fields are present and follow the expected types
- Verify
recentPosts
resolves to ≤ 3 items - Confirm no nulls or empty fields when data is expected
It should also confirm:
- Each resolver is hit as expected (tracked via trace logs or debugger sessions)
- The response shape matches the schema, especially after changes
Schema validation and drift checks
Schema introspection tools or type-safe codegen (e.g., GraphQL Code Generator) help detect contract drift:
- Run automated schema checks in CI
- Confirm that
recentPosts(limit: Int)
exists and returns expected types - Validate argument types, nullability, and nested object shape
Test error paths
GraphQL returns a 200 OK
even for partial failures. Tests must inspect the errors
array in the response.
Example 1 – typo in query:
{
"errors": [
{
"message": "Cannot query field \"recentPostz\" on type \"User\""
}
],
"data": null
}
Example 2 – invalid or expired token:
{
"errors": [
{
"message": "Authentication required"
}
],
"data": null
}
Ensure:
- Schema and field errors are properly surfaced
- Auth errors return helpful messages
- Sensitive info is not leaked in errors
Include cases like missing required arguments, type mismatches, and insufficient user permissions.
Tracing resolver execution
GraphQL queries often span multiple microservices, which makes it difficult to trace where a failure or slowdown occurred. When something breaks, the biggest challenge is often pinpointing which resolver caused the issue. Look for:
- Resolver timing logs (e.g.,
recentPosts took 412ms
) trace-id
orrequest-id
in each backend call
Stitching these clues together manually is time-consuming and error-prone. Resolver logs may be buried deep in nested services, and trace IDs might not propagate cleanly across all systems. Even when all the pieces exist, correlating them into a coherent picture often requires jumping between dashboards, reading log lines out of context, and making educated guesses about the event sequence.
Tools like Multiplayer’s full-stack session recordings alleviate these issues by capturing:
- Resolver timelines
- Query variables
- Backend service logs
- Auto-generated test scripts for replays
This allows engineers to quickly pinpoint bottlenecks, understand the full context of the failure, and rerun problematic queries without having to search through scattered logs.
Recommended tools
Avoid manual GUIs or API clients for regular testing. Instead, consider utilizing the following combination of tools:
Tool | Use Case |
---|---|
Multiplayer notebooks | Execute API queries and validate assertions inline |
GraphQL Playground | Lightweight in-browser query runner |
Apollo Client test utilities | Unit test individual resolvers |
Python or JS test runners | End-to-end API tests (e.g., with Jest, Pytest, etc.) |
Multiplayer notebooks are especially powerful for debugging and collaboration. They enable you to document query logic, store query variables, capture full tracebacks, and attach backend logs in one place, making bugs easier to understand and reducing the need for back-and-forth during triage.
Interact with full-stack session recordings to appreciate how they can help with debugging
EXPLORE THE SANDBOX(NO FORMS)
Advanced test scenarios
Once basic query validation is in place, it's important to test more complex behaviors that reflect real-world usage. Consider covering the following advanced cases:
Chained queries
Query for a user, then reuse the ID to fetch comments, likes, etc. Validate consistency across steps.
Conditional rendering logic
Test how data exposure varies by user roles or feature flags.
Schema evolution
Validate that new optional fields don't break existing clients
Resolver load tests
Check how long nested or paginated resolvers take under load.
Replay for regression testing
Use captured queries + trace sessions as repeatable test cases. For example:
- Replay a slow query to confirm resolver optimization.
- Replay a broken query from production and verify the fix.
Multiplayer’s full-stack session recordings enable this workflow by recording the entire query context and turning it into runnable scripts.
gRPC API testing examples
gRPC is widely used for backend-to-backend communication. It uses a compact binary format (Protocol Buffers) and requires a different testing strategy than REST or GraphQL.
Scenario
A fintech service provides an account lookup API over gRPC. The service includes a method called GetAccountDetails
that accepts an account ID and returns account information, such as balance, status, and owner name.
Simulating Proto payloads
One way to test gRPC without writing client code is by using grpcurl, a command-line tool that allows you to send requests in JSON format. For example:
grpcurl -d '{ "id": "account123" }' \
-H "Authorization: Bearer exampleToken123" \
localhost:50051 fintech.AccountService/GetAccountDetails
Expected output:
{
"name": "Alice",
"balance": 2500.75,
"status": "ACTIVE"
}
This confirms the gRPC server received and processed the binary protobuf request correctly.
For deeper validation, use language-specific client stubs (e.g., Java, Go, Python). This involves the following steps:
- Set up client-side metadata
- Inject test headers (auth tokens, trace IDs)
- Verify response values, types, and enums
- Integrate into CI suites
For example, in the example above, developers should test that status
is a valid enum, balance
is numeric and ≥ 0, and name
is non-empty.
Metadata and header validation
gRPC uses metadata for cross-cutting concerns, such as authentication (Authorization
), tracing (X-Request-ID
,trace-id
), and user context (locale
,user-settings
). Because of this, it is important to test the following scenarios:
- Requests without required headers should return
UNAUTHENTICATED
- Invalid tokens should return
PERMISSION_DENIED
- Headers should propagate through internal hops. Check backend logs for IDs like
X-Request-ID
ortrace-id
Error and chaos testing
To ensure resilience in the system, you should simulate common error cases like invalid or missing account IDs (expect NOT_FOUND
), bad proto payloads (expect INVALID_ARGUMENT
), dropped metadata like missing trace IDs, and downstream dependency failures (e.g., DB timeouts returning UNAVAILABLE
).
Tests should ensure that:
- Each error is returned with the correct gRPC status code
- Error messages are safe and helpful
- Retry headers or exponential backoff logic are respected (if implemented)
One click. Full-stack visibility. All the data you need correlated in one session
RECORD A SESSION FOR FREELatency and tracing validation
Because gRPC is used in latency-sensitive environments, tests should validate timing. Attach trace ID headers to each request, record client-side timestamps, and measure response time using logs or distributed tracing tools. Validate that services meet their SLAs (e.g., under 200ms). You can use tools like Jaeger or OpenTelemetry to ensure that request traces show:
- End-to-end duration across service hops
- Bottlenecks in resolvers or downstream calls
- Clear correlation between client requests and backend logs
Multiplayer’s full-stack session recordings also support gRPC trace replay, which can help your team capture full request/response and trace context, re-run failed requests, and validate fixes against past failure traces.
Recommended tools
To effectively implement these testing strategies, it helps to have the right tools at each layer. Here’s a comparison of popular tools and where they fit:
Tool | Use case |
---|---|
grpcurl | Quick request testing via CLI. |
Multiplayer | Scriptable request testing with inline assertions and full trace capture. |
gRPCui | Web-based test UI for gRPC. |
BloomRPC | Desktop GUI for structured testing. |
Code clients | Deep integration into pipelines. |
WebSocket API testing examples
WebSockets enable real-time, bidirectional communication and are frequently used in chat apps, real-time games, and systems that require live updates. Unlike stateless request-response APIs, WebSocket testing involves validating persistent connections, message flows, and stateful interactions over time.
Scenario
In a messaging app, users connect via WebSocket to join chat rooms. Clients can send and receive messages. The server broadcasts new messages to all users connected to a given room.
Step-by-step test flow
1. Connect to the server
const ws = new WebSocket("wss://chat.example.com?token=exampleToken123");
In this step, verify that the connection is established and that the auth token is accepted if it is valid or rejected if it is invalid.
2. Send and receive messages
Client A sends:
{ "roomId": "abc123", "message": "Hello, world!" }
Client B receives:
{ "sender": "clientA", "roomId": "abc123", "message": "Hello, world!", "timestamp": "2025-07-13T12:00:00Z" }
Assert that the message content matches, timestamps, and metadata are present, and that clients in other rooms don’t receive the message.
You should also validate the order and timing in this flow. Send messages "msg1"
, "msg2"
, "msg3"
in order and confirm that the received order is preserved and that there are no delays or duplication. Attach a trace-id
in each message for backend tracking, and validate that timestamps are consistent across client logs and server logs.
3. Simulate drop and reconnect scenarios
Force-close the socket, then reconnect:
ws.close(); // simulate loss
// Then reconnect with the same user
const ws2 = new WebSocket("wss://chat.example.com?token=exampleToken123");
Verify that the new connection is successful, the previous session was properly cleaned up without message duplication, and that any missed messages (if present) are handled or recovered according to application logic.
Other testing scenarios
Multi-client consistency
To ensure reliable real-time behavior, simulate multiple clients (3 or more) connecting to the same room. Take turns sending messages from each client and confirm that all others receive them in real time. Finally, simulate one client with a poor network connection to verify whether it’s gracefully handled (e.g., by using auto-retry or reconnect logic).
Message and state validation
Each message received should include essential metadata: sender ID, timestamp, room ID, and the message body. Validate that messages conform to the expected schema, respect rate limits, and do not expose any sensitive data in the payload.
Chaos and edge case testing
Test the system’s resilience by injecting fault conditions:
- Send malformed messages and expect either a proper error response or disconnection
- Simulate long idle periods to verify heartbeat or ping/pong logic
- Flood the system with messages to trigger rate limiting (e.g., a 429 response or silent throttling)
- Use expired tokens to confirm that unauthorized clients are disconnected
Logging and observability
It is also important to confirm routing paths, inspect retry behavior for dropped messages, and monitor end-to-end latency across clients. To do so, tag test messages with trace-id
and match them against backend logs. During these tests, you should also track metrics such as server memory usage, CPU usage, open connection count, and message throughput and latency (p95, maximum latency).
Stop coaxing your copilot. Feed it correlated session data that’s enriched and AI-ready.
START FOR FREERecommended tools
Testing WebSockets requires a mix of manual, automated, and trace-driven testing approaches. Consider using the tools below:
Tool | Use Case |
---|---|
wscat | CLI for sending and receiving messages |
Node.js ws | Automated tests using scripting |
WebSocket libraries in Python, Java, etc. | Integration testing in CI/CD |
Multiplayer notebooks | Manual WebSocket testing, document and automate flows, attach traces, reuse variables |
Multiplayer full-stack session recordings | Capture and replay full WebSocket sessions, inspect message traces, and validate real-time fixes |
General API testing tips
While an exhaustive list is not feasible to include, these practices apply across a broad scope of use cases and help APIs remain reliable, secure, and maintainable.
Test broken flows and edge cases
Include negative scenarios, such as invalid credentials, missing fields, or incorrect formats. APIs should return clear error messages, such as 401 Unauthorized or 400 Bad Request. In distributed systems, also test what happens when one service fails. For example, if a payment system is offline, the checkout API should return a graceful error rather than timing out or crashing.
Include negative and failure scenarios such as:
- Invalid credentials or expired tokens
- Malformed JSON or mismatched data types
- Missing required fields
- Requests with insufficient permissions (403 errors)
- API calls made while dependencies are down
Edge cases often reveal reliability issues that aren’t visible in happy-path tests.
Automate key tests in CI
Convert critical flows into automated tests that run on every build. Even if you don’t automate everything, covering the main user journeys will help identify and address breaking changes across services. Include tests for both success and failure cases, and check not only outputs but also system behavior and latency.
Not every test needs to run on every commit. Use tagging to optimize CI:
@smoke
- basic functionality and uptime tests@regression
- critical flows from previous bugs@contract
- schema validation across services@chaos
- stability and failure testing
A common practice is to run smoke tests on every commit and reserve full suites for nightly builds or pre-release pipelines. This helps keep test runs fast without sacrificing coverage.
Use realistic data
Match production-like data in your tests. Use realistic payload sizes, values, and nesting levels. This helps uncover issues such as field truncation, performance bottlenecks, or encoding problems that small test payloads may miss.
Verify security and rate limits
Ensure protected APIs reject unauthenticated requests. Also, check that role-based permissions are enforced. Simulate rapid requests to test throttling behavior and confirm the system returns a 429 or equivalent when limits are exceeded. Include tests for unauthorized access (expect 401), role-based access violations (expect 403), and rate limiting enforcement (conduct burst tests and expect 429 or server throttling).
In addition, test token scope restrictions by simulating users with underprivileged tokens attempting to access higher-scope operations. As a general rule, keep in mind that security gaps often appear in corner cases or non-standard flows.
Monitor while running tests
Monitor dashboards during test runs. Even functional tests can trigger memory spikes or CPU usage. If a simple query causes a backend load spike or increases the error rate, it's worth investigating. Tie each test to a trace ID or tag so you can isolate its effect on the system, and watch for metrics like:
- CPU, memory, and I/O usage
- Spikes in response latency (p95, p99)
- Increased retry or error rates
- Backend saturation (e.g., connection pool exhaustion)
Conclusion
Testing modern APIs goes far beyond checking status codes or simple responses. It involves validating full workflows, understanding service dependencies, and tracing problems across systems. These patterns apply across architectures and help ensure your APIs perform correctly, even when multiple services are involved.
To improve coverage and reliability, focus on chaining related calls, capturing test sessions, testing edge cases, and sharing reproducible examples with your team. Tools like Multiplayer’s notebooks and full-stack session recordings can simplify the process by recording full request/response flows and automatically generating test scripts.
With the right strategy, API testing becomes more than a checklist. It becomes a system-level safety net that supports faster development and fewer production issues.