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.
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
Most teams only ever do the first one. The second is what stops your mocks quietly turning into fiction.
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.
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
By Peter Thomas, creator of Karate
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.
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
# 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.
# 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 fullMock Fidelity
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.
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
Same syntax. Same IDE. Same repo. Your mocks live next to your tests, version-controlled, reviewable, and shareable across the team.
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.
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.
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.
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.
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.
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.
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
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.
The provider is verified, and the double is verified against the provider.
Load, auth, and abuse cases are tested, not assumed.
Bad input, missing fields, and error paths are covered, not just the happy case.
You assert the values that come back, not merely that a 200 came back.
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
The real-world situations where mocking earns its keep.
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.
Stop chasing flaky staging environments. Mock the unstable external service locally, make your tests deterministic, and ship with confidence.
Drive load tests against a mock instead of staging, and inject latency deliberately to see how your service behaves when a dependency slows down.
Agree the contract, build the mock first, and let frontend and backend teams develop in parallel against the same simulated endpoint.
Comparison
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 |
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.