Skip to main content

Testing Stateful APIs with Drift

Many APIs appear to require multiple steps to test. For example:

  • Creating a user before testing GET /users/{id}
  • Running POST /checkout/orders/{id} before checking /checkout/status/{id}
  • Polling until a job completes

These patterns suggest workflow testing. Drift is not a workflow engine. Drift validates contract conformance, and it does so by testing each operation independently, with state prepared beforehand.

This page explains the core concept behind testing stateful and async APIs with Drift.

What "Stateful" Means in Drift

A stateful API is one whose response depends on prior conditions. Examples include:

  • A resource that may or may not exist
  • An async job that moves from pendingreadycompleted
  • Feature flags or modes that change response shapes

Drift fully supports testing these scenarios. What Drift does not do is run a sequence of API calls to reach those states.

Drift tests states, not flows.

Why Teams Expect Sequential Tests

Developers often assume that a test must produce state before checking it:

  • "To test GET /users/{id}, I must call POST /users first."
  • "To test /checkout/status/{id}, I need to run POST /checkout/orders/{id} first."

This instinct is natural, but it stems from functional testing, where flows are executed end‑to‑end. Contract testing has a different purpose.

Contract tests must be:

  • isolated
  • deterministic
  • fast
  • independent of side effects

Chaining steps introduces nondeterminism and hides bugs. Drift avoids these problems by treating each state as a separate scenario.

How Drift Handles Stateful Behaviour

Drift verifies the behaviour of each operation exactly as described in the OpenAPI specification. To support stateful scenarios, Drift relies on state injection, not sequential calls.

❌ Not supported (workflow execution)

POST /checkout/orders/{id} → extract ID
HEAD /checkout/status/{id} → poll
GET /result/{id}

✔ Supported (state‑based verification)

Prepare state → call /checkout/status/{id} → validate documented response

Each test is independent and validates one defined state.

How to Inject State for Contract Tests

State is prepared before Drift calls your real API. Common approaches include:

  • Lua hooks — call helper endpoints or internal tools to set state
  • Non‑production helper endpoints — explicitly create or modify state
  • Datasets — provide stable IDs or static records
  • Seeded or mocked environments — e.g., workers or job runners preconfigured for specific states

This allows Drift to test stateful behaviour without executing workflows.

Example: Asynchronous Request–Reply APIs

Async APIs often follow this pattern:

  1. POST /checkout/orders/{id} → returns a job ID (202 Accepted)
  2. HEAD /checkout/status/{id} → returns job status (pending, ready, completed)
  3. GET /result/{id} → returns final output

Instead of chaining these operations:

  • Test POST /checkout/orders/{id} independently
  • Test each /checkout/status/{id} state independently
  • Test GET /result/{id} independently

Each test prepares the required state ahead of time.

Full Example: Abstract Async Workflow

This expanded example shows how state‑based contract testing applies to an asynchronous workflow. It's loosely based on the PactFlow AI architecture, with internal systems such as storage, authentication, and workers represented as a single Processing Service for brevity.

How Drift Tests These States

Drift does not reproduce this workflow. Instead, it creates four independent contract tests:

  1. POST /generate → 202 Accepted — validates the job token
  2. GET /status/{id} → 202 Pending — state prepared via Lua or helper endpoint
  3. GET /status/{id} → 200 Ready — job pre‑set to ready
  4. GET /result/{id} → 200 OK — outputs pre‑seeded

Each test validates only the response defined in the OpenAPI specification for that state.

Summary

  • Drift tests contract conformance, not workflow logic
  • Drift supports stateful testing through state injection, not chaining
  • Stateful and async flows become independent state scenarios
  • Use functional or integration tests for full workflow execution

To apply these ideas in practice, see: