Skip to main content

WireMock vs Prism for Integration Testing

The failure mode that drives engineers to this comparison is specific: integration tests start breaking in a way that is hard to locate. Either the mock returns something the contract never declared — so a passing test is meaningless — or the mock cannot reproduce a multi-step sequence and tests are forced to hit a shared environment. Both are real problems, and they point to different tools.

This guide is part of Mock Server Strategies. It treats WireMock 3 and Prism 5 as complementary instruments rather than as substitutes, lays out exactly when each is the right choice, and shows annotated examples of both so you can decide without guessing.

WireMock vs Prism decision diagram Two columns showing the primary driver for each tool: Prism is driven by the OpenAPI spec and validates every request and response against it; WireMock is driven by hand-written stub mappings and provides full behavioral control including stateful scenarios. Choosing a Mock Server Strategy Start from your primary driver — spec fidelity or behavioral control Need stateful sequences?

Yes No

Prism 5 Spec-driven Validates requests and responses zero extra config WireMock 3 Stub-driven Stateful scenarios Header/body matchers full behavioral control Use Both Together Prism as validation proxy in front of WireMock: spec fidelity + stateful stubs in the same pipeline

Framing the Decision

WireMock and Prism solve adjacent but non-overlapping problems. Confusing them leads to the situations at the top of this guide.

Prism 5 is spec-driven. Give it an OpenAPI document and it serves a conformant mock with zero additional configuration: it reads the schema, picks a named example when one exists, or fabricates a valid payload from the schema when none does. More important, it validates incoming requests against the spec in real time and rejects non-conforming ones with a machine-readable error body. This makes it a contract-enforcement tool as much as a mock — the test fails at the right layer rather than somewhere downstream.

WireMock 3 is stub-driven. You define request matchers and response bodies explicitly in JSON mapping files (or via its API), and WireMock returns exactly what the mapping says. No spec is consulted. What you gain is full behavioral control: stateful scenario transitions, response templating that echoes request fields back, latency injection, conditional branching on headers, and fault injection. What you lose is automatic spec conformance — a WireMock stub can return anything, including shapes that contradict the contract.

The decision is rarely “which one” and more often “which one owns which layer”:

  • Let Prism own spec fidelity and request validation.
  • Let WireMock own behavior the contract cannot describe.
  • Run them together when you need both.

When to Use Each Tool

Reach for Prism when:

  • The API has an OpenAPI 3.x document with named response examples and you want the mock populated from that document automatically — no mapping files to maintain.
  • You practice schema-first design and need the spec to be the single source of truth; any stub that diverges from the spec is a defect, not a feature.
  • You need request-level spec enforcement in integration tests: consumers that send malformed requests should fail immediately, not produce a meaningless 200.
  • CI startup time matters and the team does not have a JVM on the agent — Prism runs via npx in under two seconds.
  • You are generating the mock as part of a broader OpenAPI Specification Deep Dive workflow and want the mock updated automatically when the spec changes.

Reach for WireMock when:

  • You need to mock a multi-step stateful flow — for example, a checkout that starts as PENDING, transitions to PROCESSING after a POST, then to SHIPPED after another call. Prism has no server-side state; WireMock scenarios handle this natively.
  • The target API does not have an OpenAPI spec, or the spec lags so far behind the real implementation that driving a mock from it would give false confidence.
  • You need fine-grained conditional behavior: return a 429 on the third request, inject a 500ms delay on a specific path, or branch on a custom header value — behaviors a schema cannot encode.
  • Your test suite is JVM-based (JUnit, Spring Boot) and you want WireMock as a @RegisterExtension in-process server with zero network overhead.
  • You need fault injection (connection resets, truncated responses, deliberately malformed JSON) to test resilience in the consumer.

Use both when:

  • You have stateful flows (WireMock) but also want every request and response validated against the spec. Run Prism in proxy mode upstream of WireMock: Prism validates the request before forwarding it, and validates the response before returning it. The WireMock stub provides the behavior; Prism enforces the contract.

Comparison Table

Dimension Prism 5 WireMock 3
Mock source OpenAPI / AsyncAPI spec Hand-written JSON stub mappings
Spec-driven response Yes — schema or named example No — author-defined only
Request validation vs spec Yes — rejects on schema violation No — only matcher assertions
Response validation vs spec Yes (proxy mode) No
Stateful scenarios No server-side state; Prefer header only Yes — named scenario state machines
Conditional on headers/body Prefer header for code/example selection Full matcher DSL (regex, JSONPath, XPath)
Response templating No Yes — Handlebars, echo request fields
Fault injection No Yes — connection reset, bad chunk, delay
Language/runtime Node.js (npx @stoplight/prism-cli@5) JVM (JAR, JUnit 5 extension) or Docker
CI startup time ~2 s (npx, no extra config) ~3-5 s (JAR + mappings)
Setup overhead Zero: one CLI invocation against the spec Low-medium: write mapping files per scenario
Best fit Spec-true mock, request/response guardrail Edge cases, stateful flows, resilience testing

Annotated Examples

WireMock 3 — Stateful Stub for an Order Checkout Flow

The scenario below models a three-state order lifecycle. WireMock’s scenario mechanism tracks which state the server is in and transitions it on each matching request. No spec involved; the stub defines the contract in code.

// mappings/checkout-scenario.json
[
  {
    "scenarioName": "OrderCheckout",
    "requiredScenarioState": "Started",
    "request": {
      "method": "POST",
      "urlPath": "/orders"
    },
    "response": {
      "status": 201,
      "headers": { "Content-Type": "application/json" },
      "jsonBody": { "id": "ord_123", "status": "PENDING" }
    },
    "newScenarioState": "OrderCreated"
  },
  {
    "scenarioName": "OrderCheckout",
    "requiredScenarioState": "OrderCreated",
    "request": {
      "method": "POST",
      "urlPath": "/orders/ord_123/pay"
    },
    "response": {
      "status": 200,
      "headers": { "Content-Type": "application/json" },
      "jsonBody": { "id": "ord_123", "status": "PROCESSING" }
    },
    "newScenarioState": "OrderPaid"
  },
  {
    "scenarioName": "OrderCheckout",
    "requiredScenarioState": "OrderPaid",
    "request": {
      "method": "GET",
      "urlPath": "/orders/ord_123"
    },
    "response": {
      "status": 200,
      "headers": { "Content-Type": "application/json" },
      "jsonBody": { "id": "ord_123", "status": "SHIPPED" }
    }
  }
]
# Start WireMock 3 with the mappings directory
java -jar wiremock-standalone-3.9.1.jar --port 8089 --root-dir .

# Step 1: create order → state transitions to OrderCreated
curl -s -X POST http://localhost:8089/orders
# => {"id":"ord_123","status":"PENDING"}

# Step 2: pay → state transitions to OrderPaid
curl -s -X POST http://localhost:8089/orders/ord_123/pay
# => {"id":"ord_123","status":"PROCESSING"}

# Step 3: poll status → returns SHIPPED (OrderPaid state matched)
curl -s http://localhost:8089/orders/ord_123
# => {"id":"ord_123","status":"SHIPPED"}

Why the scenario mechanism matters: a single static stub for GET /orders/ord_123 could only ever return one state. The scenario transitions ensure that the consumer’s polling logic receives the correct state at each step, making the integration test reflect the real provider’s behavior rather than a frozen snapshot.

Prism 5 — Spec-Driven Mock with Request Validation

Prism reads the OpenAPI document and validates every request before serving a response. The only configuration is the spec file; there are no mapping files to keep in sync.

# Install Prism 5 CLI
npm install --save-dev @stoplight/prism-cli@5

# Serve the spec; static mode returns named examples verbatim
npx prism mock openapi.yaml --port 4010

# Valid request — returns the named example
curl -s http://localhost:4010/orders/ord_123
# => {"id":"ord_123","status":"SHIPPED","total":4200}

# Request a specific status code via the Prefer header
curl -s http://localhost:4010/orders/ord_123 -H 'Prefer: code=404'
# => {"code":"NOT_FOUND","message":"Order not found"}

# Request that violates the path parameter pattern (^ord_[a-z0-9]+$)
curl -s http://localhost:4010/orders/BAD-ID
# => 422 — Prism rejects before responding
# {"type":"https://stoplight.io/prism/errors#UNPROCESSABLE_ENTITY",
#  "title":"Invalid request",
#  "detail":"Your request/response is not valid",
#  "validation":[{"location":["path","id"],
#    "severity":"Error",
#    "code":"pattern",
#    "message":"must match pattern \"^ord_[a-z0-9]+$\""}]}

The 422 with instancePath naming the failing field is the critical detail. The consumer test fails immediately with a precise error, rather than propagating a malformed request to the stub and receiving a potentially misleading response. Prism in proxy mode extends this to response validation — any stub or upstream that returns a body violating the schema is also rejected before the consumer receives it.

Before/After: Wrong Tool vs Right Tool

Scenario: Testing a consumer that depends on an order’s status changing after payment

Wrong tool (Prism only):

# Prism has no state machine. Every GET /orders/ord_123 returns SHIPPED
# regardless of whether /orders/ord_123/pay was called first.
npx prism mock openapi.yaml --port 4010

curl -s -X POST http://localhost:4010/orders/ord_123/pay
# Prism returns the example for POST /orders/{id}/pay — fine

curl -s http://localhost:4010/orders/ord_123
# => {"id":"ord_123","status":"SHIPPED"}
# Returns SHIPPED even on the first call, before payment.
# The consumer test passes — but the real provider would return PENDING here.

This test gives a false green. The consumer never finds out it is handling the pre-payment state incorrectly because Prism cannot distinguish “before pay was called” from “after pay was called.”

Right tool (WireMock with scenarios):

java -jar wiremock-standalone-3.9.1.jar --port 8089 --root-dir .

# State machine starts in "Started"
curl -s http://localhost:8089/orders/ord_123
# => {"id":"ord_123","status":"PENDING"}   ← correct pre-payment response

curl -s -X POST http://localhost:8089/orders/ord_123/pay
# => {"id":"ord_123","status":"PROCESSING"}  ← state transitions to OrderPaid

curl -s http://localhost:8089/orders/ord_123
# => {"id":"ord_123","status":"SHIPPED"}   ← correct post-payment response

The test now verifies the consumer handles each state transition correctly. If the consumer misread the PENDING state and crashed, this test catches it. The Prism version would never surface that bug.

Bonus: add spec validation on top with Prism proxy

# Start WireMock for behavior, then Prism as a validating proxy in front of it
java -jar wiremock-standalone-3.9.1.jar --port 8089 --root-dir .
npx prism proxy openapi.yaml http://localhost:8089 --port 4011

# Now run tests against :4011 — Prism validates every request and response
# against the spec; WireMock provides the stateful behavior.
curl -s http://localhost:4011/orders/ord_123
# Prism validates the request → forwards to WireMock → validates the response
# → returns the response to the consumer.

This is the “use both” pattern: WireMock owns the state machine, Prism owns spec conformance. A WireMock stub that returns a shape the spec forbids will now cause the integration test to fail at the right layer.

Verification

After configuring either tool, confirm the mock behaves as expected before wiring it into CI.

# Prism: assert request validation is active
curl -s -w '\n%{http_code}\n' http://localhost:4010/orders/INVALID
# Expect: 422 — proves Prism is enforcing the path pattern

# Prism: assert a named example is returned
BODY=$(curl -s http://localhost:4010/orders/ord_123)
echo "$BODY" | grep -q '"status":"SHIPPED"' && echo "PASS" || echo "FAIL"

# WireMock: verify scenario resets between test runs
curl -s -X POST http://localhost:8089/__admin/scenarios/reset
# => 200; all scenarios return to "Started"

# WireMock: list registered mappings
curl -s http://localhost:8089/__admin/mappings | python3 -m json.tool | grep scenarioName
# Confirm all three checkout mappings are present

In CI, the standard pattern for Prism is:

npx prism mock openapi.yaml --port 4010 &
PRISM_PID=$!
npx wait-on http://localhost:4010/orders/ord_123
npm test          # consumer integration tests target http://localhost:4010
kill "$PRISM_PID"

For WireMock, either start the JAR as a background process or use the official Docker image so no JVM install is required on the CI agent:

docker run -d --name wiremock -p 8089:8080 \
  -v "$PWD/mappings":/home/wiremock/mappings \
  wiremock/wiremock:3.9.1

A passing CI run against Prism proves the consumer sends conformant requests and handles spec-valid responses. A passing run against WireMock scenarios proves the consumer handles state transitions correctly. Both signals are necessary for a well-integrated service.

Edge Cases and Caveats

  • Prism dynamic mode and non-repeatable assertions. Running Prism with --dynamic makes it fabricate response values from the schema rather than returning named examples. Field values change on every request. Consumer tests that assert specific field values will flicker. Use dynamic mode for exploratory testing and load generation; use static (the default) for contract and integration tests.

  • WireMock scenario resets between test runs. WireMock scenarios persist across requests during a single server session. If your test suite does not reset scenarios between tests — via POST /__admin/scenarios/reset or the JUnit @WireMockTest extension’s resetAllScenarios — a later test will start in the final state of an earlier test. This is the most common source of test-order-dependent flakiness in WireMock-based suites.

  • Prism proxy and self-signed certificates. When Prism proxies to a local WireMock or a staging server over HTTPS, it validates TLS by default. If the upstream uses a self-signed cert, Prism will refuse to connect. Pass --errors to surface this explicitly, or terminate TLS at a reverse proxy and run both tools over plain HTTP within the CI network.

Frequently Asked Questions

Can WireMock validate requests against an OpenAPI spec?

Not natively. WireMock validates only what you assert in the request matchers of each stub mapping. To add spec validation, run WireMock behind Prism in proxy mode, or use the wiremock-openapi-validation extension.

Does Prism support stateful mocking — for example, returning different responses after a POST?

Only coarsely. Prism selects a response via the Prefer header (Prefer: code=201 or Prefer: example=created) but does not maintain server-side state between calls. For state machines or multi-step flows, use WireMock scenarios.

Which tool starts faster in a CI container?

Prism starts in under two seconds via npx and needs no extra config beyond the spec file. WireMock starts in a few seconds with java -jar but requires a populated mappings directory or API calls to register stubs.

Can I use both tools together in the same pipeline?

Yes, and it is often the right answer. Run Prism as a spec-validation proxy in front of WireMock: WireMock handles stateful stubs, and Prism enforces that every request and response conforms to the OpenAPI document.

Does WireMock 3 require Java?

The standalone JAR and native binary require Java 11+. WireMock is also available as a Docker image (wiremock/wiremock:3.9.1) and as a JVM library dependency for JUnit or TestNG suites, so non-JVM teams typically reach for the Docker image.