Guide

API Testing Examples & Tutorial

Table of Contents

    Like this article?

    Subscribe to our LinkedIn Newsletter to receive more educational content

    Subscribe now

    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 typeTesting approach
    REST
    • Chain requests
    • Validate the structure and content of the JSON response
    • Trace backend causes of 500 errors using logs and session diagnostics.
    GraphQL
    • Validate queries
    • Capture schema errors
    • Trace GraphQL execution paths and related backend events using test session replays.
    gRPC
    • Construct gRPC-style binary-equivalent payloads using JavaScript.
    • Analyze backend response latency through request logs and traces.
    WebSocket
    • Send/receive messages
    • Replay unstable connection behavior using logs and backend traces.

    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 ID
    • status 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 more
    Fix bugs faster with all the data you need, correlated right out-of-the-box
    Turn a full stack session recording into the perfect AI prompt
    Understand your system end-to-end, to develop and test with confidence

    Final 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 or request-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.

    Avoid manual GUIs or API clients for regular testing. Instead, consider utilizing the following combination of tools:

    ToolUse Case
    Multiplayer notebooksExecute API queries and validate assertions inline
    GraphQL PlaygroundLightweight in-browser query runner
    Apollo Client test utilitiesUnit test individual resolvers
    Python or JS test runnersEnd-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:

    1. Set up client-side metadata
    2. Inject test headers (auth tokens, trace IDs)
    3. Verify response values, types, and enums
    4. 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 or trace-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 FREE

    Latency 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.

    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:

    ToolUse case
    grpcurlQuick request testing via CLI.
    MultiplayerScriptable request testing with inline assertions and full trace capture.
    gRPCuiWeb-based test UI for gRPC.
    BloomRPCDesktop GUI for structured testing.
    Code clientsDeep 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 FREE

    Testing WebSockets requires a mix of manual, automated, and trace-driven testing approaches. Consider using the tools below:

    ToolUse Case
    wscatCLI for sending and receiving messages
    Node.js wsAutomated tests using scripting
    WebSocket libraries in Python, Java, etc.Integration testing in CI/CD
    Multiplayer notebooksManual WebSocket testing, document and automate flows, attach traces, reuse variables
    Multiplayer full-stack session recordingsCapture 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.

    Like this article?

    Subscribe to our LinkedIn Newsletter to receive more educational content

    Subscribe now

    Continue reading this series