Mock Stripe, Twilio, and any external API without changing your code

The problem with traditional mocking

When your service calls Stripe to charge a card, you don’t want to actually charge anyone during tests. The standard approach is to add a layer of indirection — an SDK wrapper, a feature flag, or an environment variable that swaps the real Stripe client for a fake one.

This works, but it has costs:

Network-level mocking

Dokkimi takes a different approach. Instead of mocking at the application layer, it mocks at the network layer. Your service makes a real HTTP call to api.stripe.com, but DNS within the test namespace resolves that domain to Dokkimi’s mock handler.

items:
  - type: MOCK
    name: mock-stripe
    mockTarget: api.stripe.com
    mockPath: /v1/charges
    mockResponseStatus: 200
    mockResponseBody:
      id: ch_test_123
      status: succeeded
      amount: 1998
    mockResponseHeaders:
      content-type: application/json

Your service code doesn’t change at all. It constructs the same HTTP request it would in production, sends it to the same hostname, and gets back a response that looks exactly like what Stripe would return.

Asserting on outbound calls

Because the mock is part of Dokkimi’s interception layer, every call to the mock is captured. You can assert on exactly what your service sent:

assertions:
  - match:
      origin: payment-service
      method: POST
      url: api.stripe.com/v1/charges
    assertions:
      - path: request.body.amount
        operator: eq
        value: 1998
      - path: request.body.currency
        operator: eq
        value: 'usd'

This lets you verify that your service is calling external APIs correctly — right endpoint, right body, right headers — without needing access to the external service’s logs.

Multiple mocks in one test

You can define as many mocks as your test needs:

items:
  - type: MOCK
    name: mock-stripe
    mockTarget: api.stripe.com
    mockPath: /v1/charges
    mockResponseStatus: 200
    mockResponseBody:
      id: ch_test_123

  - type: MOCK
    name: mock-twilio
    mockTarget: api.twilio.com
    mockPath: /2010-04-01/Accounts/*/Messages.json
    mockResponseStatus: 201
    mockResponseBody:
      sid: SM_test_456

Each mock operates independently. Your checkout flow can call Stripe for payment and Twilio for SMS confirmation, and both calls are intercepted, mocked, and available for assertions.

Error scenarios

Mocks are especially useful for testing error handling. What happens when Stripe returns a 402? When Twilio times out? Define a separate test with a mock that returns the error response:

- type: MOCK
  name: mock-stripe-decline
  mockTarget: api.stripe.com
  mockPath: /v1/charges
  mockResponseStatus: 402
  mockResponseBody:
    error:
      type: card_error
      message: 'Your card was declined.'

Then assert that your service handles it correctly — returns the right error to the client, doesn’t create a half-finished order, logs the failure appropriately.

Body matching — same endpoint, different responses

Some APIs funnel all requests through a single endpoint. LLM APIs send every prompt to POST /v1/chat/completions. GraphQL APIs send every query to POST /graphql. Without body matching, you’d get one mock response for all calls to that endpoint.

Dokkimi lets you match on request body content so the same endpoint returns different responses depending on the payload:

# Classify prompt → returns "billing"
- type: MOCK
  name: mock-llm-classify
  mockTarget: api.openai.com
  mockPath: /v1/chat/completions
  mockRequestBodyContains: 'classify this ticket'
  mockResponseStatus: 200
  mockResponseBody:
    choices:
      - message:
          content: billing

# Extract prompt → returns entities
- type: MOCK
  name: mock-llm-extract
  mockTarget: api.openai.com
  mockPath: /v1/chat/completions
  mockRequestBodyContains: 'extract entities'
  mockResponseStatus: 200
  mockResponseBody:
    choices:
      - message:
          content: '{"people": ["Alice"], "places": ["NYC"]}'

# Fallback — no body match, catches everything else
- type: MOCK
  name: mock-llm-fallback
  mockTarget: api.openai.com
  mockPath: /v1/chat/completions
  mockResponseStatus: 200
  mockResponseBody:
    choices:
      - message:
          content: "I don't understand."

Mocks with a body match automatically outrank mocks without one (higher specificity score), so the fallback only fires when no body-matching mock matches. Use mockRequestBodyContains for simple substring matching (case-insensitive) or mockRequestBodyMatches for regex patterns.

When not to use mocks

Mocks are for functional testing — verifying that your code handles success and failure paths correctly. They’re not a substitute for:

The sweet spot is using mocks for the majority of your tests (fast, deterministic, free) and reserving real API calls for a small set of smoke tests.