API Mocks

Stateful API mocks
in the same syntax
as your tests.

A mock is only useful if it behaves like the thing it replaces. Karate mocks hold state, so a working CRUD double is a dozen lines rather than a plugin. 100% self-hosted, version-controlled, thread-safe.

Then prove it still matches the real service.

users-mock.feature
Feature: User Service Mock

# Background runs once, on startup.
# That is what makes the mock stateful.
Background:
  * def users = {}
  * def nextId = 1

Scenario: pathMatches('/users') && methodIs('post')
  * def user = request
  * user.id = nextId
  * users[nextId + ''] = user
  * def nextId = nextId + 1
  * def responseStatus = 201
  * def response = user

Scenario: pathMatches('/users/{id}') && methodIs('get')
  * def user = users[pathParams.id]
  * def responseStatus = user ? 200 : 404
  * def response = user || { error: 'Not found' }

Contract Testing

A contract test has two jobs

Most teams only ever do the first one. The second is what stops your mocks quietly turning into fiction.

01

Tell you when the provider changes

The service you depend on ships a breaking change. You want to hear about it from a red build, not from a production incident three weeks later.

02

Prove your test double still stands in

Every test you run against a mock is only as trustworthy as the mock. If nothing verifies the mock against the real service, your suite is green against a service that no longer exists.

Recommended reading

API Contract Testing: A Visual Guide

By Peter Thomas, creator of Karate

Read the guide

Same spec, two targets

Write the contract as an ordinary Karate feature that reads its base URL from a variable. Point that variable at the real service and you have a provider contract test. Point it at your mock and you have proof the mock is a faithful stand-in. One file, no generated pact artifacts, no broker to run.

payment-contract.feature The contract
Background:
  * url baseUrl

Scenario: create and fetch
  Given path 'payments'
  And request { amount: 25.50 }
  When method post
  Then status 201
  And match response ==
    { id: '#number', amount: 25.50 }
  * def id = response.id

  Given path 'payments', id
  When method get
  Then status 200
  And match response.amount == 25.50
against-provider.feature Run 1
# the real service

Background:
  * def baseUrl =
      'https://payments.internal'

Scenario: provider honours it
  * call read(
      'payment-contract.feature')

# Fails the moment the
# provider team ships a
# breaking change.
against-mock.feature Run 2
# your test double

Background:
  * def mock = karate.start(
      { mock: 'payment-mock.feature',
        port: 0 })
  * def baseUrl =
      'http://localhost:' + mock.port

Scenario: mock honours it too
  * call read(
      'payment-contract.feature')

Run 1 in a nightly job against the live provider. Run 2 on every commit, where it costs nothing. When run 1 goes red, you know the provider moved. When run 2 goes red, you know your mock did. Both are the same twelve lines of Gherkin.

That is the pattern by hand. Karate can also mint both legs for you in one session and return the differences classified, with the operations it never touched named individually, and a rung saying what the evidence entitles you to claim. This is spec-first: the OpenAPI document is the shared artifact, so there is no pact file and no broker.

See contract testing in full

Mock Fidelity

Most mocks fail on state

A stub that returns the same canned payload no matter what you send it cannot stand in for a real service. It can only stand in for a screenshot of one.

Low fidelity

  • ×Hardcoded payload, same response every time
  • ×Ignores what you actually sent
  • ×POST then GET returns the old value
  • ×No error paths, no auth, no edge cases
  • ×Cannot be used to test anything stateful

High fidelity what Karate enables

  • Holds state across requests, so CRUD sequences behave
  • Validates the incoming request and rejects bad input
  • Real status codes: 201, 400, 404, 409, 500
  • Conditional business logic in plain JavaScript
  • Can be verified against the real service by the same contract

In Karate the Background block of a mock runs once, at startup, not before every request. Variables defined there survive for the life of the server, which is why a stateful mock is a handful of lines rather than a plugin.

Capabilities

Mocks that feel like tests

Same syntax. Same IDE. Same repo. Your mocks live next to your tests, version-controlled, reviewable, and shareable across the team.

100% Self-Hosted

Runs on your laptop, your CI runners, or your own staging environment. There is no broker to operate and no cloud tier. Mock payloads and secrets never leave your infrastructure.

Stateful by Default

Shared state, conditional logic, and path parameter matching in pure Karate syntax. Build a working CRUD service double in a dozen lines, without writing a plugin.

Proxy Mode

Sit in front of the real service with karate.proceed(). Forward most traffic through, intercept the one endpoint you need to control, and rewrite responses on the way back.

Latency & Failure Injection

Set responseDelay to simulate a slow dependency, or return a 500 on demand to exercise your retry and timeout logic. Delays use a non-blocking scheduler, so they do not tie up threads.

Start It Anywhere

Spin a mock up inside a test with karate.start(), from Java, or standalone from the command line with hot reload while you edit. Bind to port 0 and run mocks in parallel without collisions.

Safe Defaults

Incoming request data is treated as inert. Embedded expressions and the Java bridge are both off unless you explicitly opt in, so a mock does not become an execution surface for whatever gets posted to it.

Already have an OpenAPI spec?

The IntelliJ plugin imports an OpenAPI or Swagger file and generates a mock from it, so you can start from the spec instead of a blank file. Available in the plugin's PRO tier; the mock server itself is open source.

Where It Fits

Contract testing is the top of the ladder

It is not a starting point, and any vendor who tells you otherwise is selling something. Each rung below has to hold before the one above it means anything.

05

Contract testing

The provider is verified, and the double is verified against the provider.

04

Performance and security coverage

Load, auth, and abuse cases are tested, not assumed.

03

Negative testing

Bad input, missing fields, and error paths are covered, not just the happy case.

02

Data validation

You assert the values that come back, not merely that a 200 came back.

01

Happy path tests

The endpoints are called at all, and something checks the result.

The advantage of doing this in Karate is that every rung uses the same syntax. You are not adopting a new tool at level five, you are pointing tests you already have at a second target.

Use Cases

When you need a test double

The real-world situations where mocking earns its keep.

Dependencies owned by other teams

When you cannot block on another squad's service being ready, mock their contract and move forward. Swap in the real service later without changing your tests.

Unreliable test environments

Stop chasing flaky staging environments. Mock the unstable external service locally, make your tests deterministic, and ship with confidence.

Performance testing

Drive load tests against a mock instead of staging, and inject latency deliberately to see how your service behaves when a dependency slows down.

Contract-first development

Agree the contract, build the mock first, and let frontend and backend teams develop in parallel against the same simulated endpoint.

Comparison

How the approaches differ

Pact solves contract generation. WireMock solves stubbing. Karate does both with the tests you already write.

Capability Karate Pact WireMock Postman Mocks
One spec runs against mock and real service Yes, same file Separate pact artifact No No
Stateful mocks (CRUD, sequencing) Built in Provider states, limited Via extensions Limited
Asserts response values, not just shape Yes Matcher-based, shape first Not its job Limited
Proxy the real service, intercept selectively karate.proceed() No Yes, record and playback No
Latency and failure injection Non-blocking delays Not a goal Yes Limited
Extra infrastructure to operate None, files in your repo Pact Broker None Cloud workspace
Fully self-hosted Yes Self-host or SaaS broker Yes No, cloud only
Same syntax as your existing tests Yes, unified Per-language DSL Java or JSON config Separate UI

Trust your mocks again.

Write the contract once. Hold the provider to it, hold your test double to it, and find out the day they diverge instead of the day it matters.