Resource library

QA How-To

WireMock vs MockServer API Testing (2026)

Compare WireMock vs MockServer API testing with runnable Docker examples for matching, failures, verification, debugging, and the right 2026 tool choice.

18 min read | 3,063 words

TL;DR

WireMock is the default recommendation for Java-heavy test suites and teams that value simple standalone mappings and near-miss diagnostics. MockServer is stronger when you need first-class proxy workflows, expectation priorities and lifetimes, broad client-language support, or rich traffic retrieval. Both can match requests, return dynamic HTTP responses, simulate failures, and verify calls.

Key Takeaways

  • Choose WireMock for the smoothest Java and JUnit workflow, readable file mappings, and strong unmatched-request diagnostics.
  • Choose MockServer when proxying, expectation lifetimes, priorities, multi-language clients, or detailed traffic retrieval drive the design.
  • Match only behavior the client depends on, including method, path, meaningful headers, and stable body fields.
  • Verify outbound requests separately from checking the response returned by the double.
  • Reset mappings and request journals at test boundaries so parallel runs cannot contaminate one another.
  • Keep persistent mock definitions versioned and pin container images for reproducible CI runs.
  • Use mocks for controlled dependency behavior, then retain contract, integration, security, and performance tests for risks mocks cannot prove.

WireMock vs MockServer API testing comes down to the way your team builds, controls, and diagnoses HTTP test doubles. Choose WireMock when Java or JUnit integration, file-based mappings, and excellent near-miss diagnostics are central. Choose MockServer when proxying, bounded expectations, priority and time-to-live controls, or clients in several programming languages matter more.

Both tools can match methods, paths, headers, query parameters, and bodies. Both can return controlled responses, inject latency or failures, record traffic, and verify calls. The useful comparison is not a feature-count contest. It is whether the tool makes your most important dependency behavior easy to express and a failed test quick to explain.

This guide builds the same inventory and shipping doubles in both products. You will start pinned Docker images, create expectations through real control APIs, exercise success and retry paths, verify request counts, and inspect mismatches. The lab stays language-neutral, so you can later call it from REST Assured, Playwright, pytest, or any HTTP client.

TL;DR

Use WireMock as the default for a Java-heavy application or test suite. Its standalone server is simple, its JSON mapping format reads cleanly in code review, and its request journal plus near-miss endpoints make a slightly wrong request easy to investigate. JUnit users can also run it in process without maintaining a separate service.

Pick MockServer when the mock is acting as a network platform rather than only a test fixture. Expectations can have counts, lifetimes, and priorities, and the server exposes extensive proxy, retrieve, and verification workflows. Its maintained clients across multiple languages are useful when Java, JavaScript, Python, Go, .NET, Rust, Ruby, and PHP suites share the same virtualization layer.

Decision WireMock MockServer Practical verdict
Java and JUnit ergonomics Excellent Strong WireMock
Standalone JSON stubs Simple mappings and __files Initialization JSON and control API WireMock for readability
Request matching Rich matchers, JSONPath, XPath, schemas, custom extensions Rich matchers, JSON, JSONPath, schemas, XPath, logical composition Tie for common API tests
Stateful sequences Named scenarios Counts, priority, time to live Depends on how you model state
Proxy and recorded traffic work Recording and proxy support Broad forwarding, proxy, retrieve, and HAR workflows MockServer
Failure diagnosis Unmatched requests and ranked near misses Detailed logs and recorded request or exchange retrieval WireMock for match triage, MockServer for traffic analysis
Polyglot client support REST API plus ecosystem clients Officially documented clients across many languages MockServer

What You Will Build

You will create two equivalent mock APIs on your workstation:

  • A WireMock server at http://localhost:8080.
  • A MockServer instance at http://localhost:1080.
  • A strict-enough POST /orders matcher that tolerates irrelevant JSON fields.
  • A GET /shipping/quote sequence that returns 503 once, then 200.
  • Verification checks for the calls and diagnostic queries for unmatched traffic.

Use the same requests against both servers. That controls the experiment and exposes configuration differences without mixing in framework-specific client code.

Prerequisites

Install Docker Engine or Docker Desktop, curl, and jq. The commands use WireMock 3.13.2 and MockServer 7.5.0, which are the pinned versions shown in the current project documentation when this guide was prepared. Do not replace them with latest in CI because an image update can change behavior without a test-code commit.

Confirm the tools before starting:

docker version --format '{{.Server.Version}}'
curl --version | head -n 1
jq --version

Verify all three commands return a version. Docker must report a server version, not only a client version. The examples assume ports 8080 and 1080 are free. If either port is occupied, stop the conflicting process or change both the published port and every URL for that server.

You need no Maven, Gradle, or npm dependency for this lab. A later framework can create these same definitions through the Java or Node client, but the HTTP APIs reveal what the server actually receives and make each example portable.

WireMock vs MockServer API Testing Comparison

WireMock organizes standalone behavior around stub mappings. A mapping combines a request pattern and response definition, can be stored under mappings/, and can refer to larger response files under __files/. The same concepts appear in its Java DSL. This model suits teams that treat dependency behavior as readable test data checked into the consumer repository.

MockServer calls the equivalent object an expectation. An expectation can respond, forward, raise an error, or use a callback. It can also carry a remaining match count, a time to live, and a priority. Those controls are valuable when the server is shared by a larger integration environment or must model short-lived behavior without an external state machine.

Matching quality is close for normal REST work. Both understand path, method, query, headers, cookies, JSON, regular expressions, JSONPath, XML, XPath, and schema-oriented checks. WireMock's extension model is attractive when a Java team needs a custom matcher or transformer. MockServer's logical body composition and control-plane options are attractive when a platform team exposes centralized virtualization.

The biggest operational difference appears after a failure. WireMock explicitly reports unmatched requests and calculates near misses against configured stubs. This points directly to the path, header, or body predicate that prevented a match. MockServer can retrieve received requests, request-response pairs, active expectations, and logs in several formats, including HAR for recorded exchanges. That provides a wider traffic view, especially while proxying.

Execution topology should influence the decision too. An embedded server gives a component test direct lifecycle control and a dynamically allocated port, which reduces collisions but couples setup to a client library. A standalone container keeps the double language-neutral and resembles a network dependency, but the test harness must own readiness, reset, ports, and diagnostics. WireMock supports both patterns comfortably in JVM projects. MockServer also offers an in-process Java option, while its Docker and remote control model is especially useful for cross-language suites. Decide the topology first, then compare DSL convenience within that boundary.

Treat TLS as a separate proof of concept if certificate behavior is in scope. Confirm hostname validation, trust-store configuration, mutual TLS requirements, and proxy routing with the exact client used by the application. A mock that accepts plain HTTP cannot validate how production code handles a certificate chain, even if every request and response field matches.

Neither tool proves that the real provider honors your invented response. Pair important doubles with API contract testing with Pact, and retain targeted integration tests for authentication, routing, persistence, and infrastructure. A fast mock is a control mechanism, not evidence about a provider it never contacted.

Step 1: Start Both Servers

Run each product as a detached container. The names make cleanup and log commands deterministic.

docker run --rm -d \
  --name qajobfit-wiremock \
  -p 8080:8080 \
  wiremock/wiremock:3.13.2

docker run --rm -d \
  --name qajobfit-mockserver \
  -p 1080:1080 \
  mockserver/mockserver:7.5.0 \
  -serverPort 1080

WireMock listens on 8080 by default. MockServer listens on 1080, and the explicit argument documents the expected control and mock port. In a pipeline, add health checks instead of sleeping for a fixed number of seconds.

Verify readiness through each administration API:

curl -fsS http://localhost:8080/__admin/mappings | jq -e '.mappings | type == "array"'
curl -fsS -X PUT http://localhost:1080/mockserver/status | jq .

The first command prints true. The second prints a JSON status document. If curl reports a connection error, inspect docker logs qajobfit-wiremock or docker logs qajobfit-mockserver. A server that has started is not yet useful, but this checkpoint separates container startup problems from expectation errors.

Step 2: Create Equivalent Success Expectations

Reset both control planes before adding behavior. Resetting at the beginning makes this walkthrough repeatable and prevents a definition from an earlier run from winning a match.

curl -fsS -X POST http://localhost:8080/__admin/reset
curl -fsS -X PUT http://localhost:1080/mockserver/reset

Add a WireMock mapping for one inventory record:

curl -fsS -X POST http://localhost:8080/__admin/mappings \
  -H 'Content-Type: application/json' \
  -d '{
    "name": "inventory sku-42 is available",
    "request": {
      "method": "GET",
      "urlPath": "/inventory/sku-42"
    },
    "response": {
      "status": 200,
      "headers": {"Content-Type": "application/json"},
      "jsonBody": {"sku": "sku-42", "available": 7}
    }
  }' | jq -e '.id'

Create the corresponding MockServer expectation. MockServer uses PUT to upsert expectations through its control API.

curl -fsS -X PUT http://localhost:1080/mockserver/expectation \
  -H 'Content-Type: application/json' \
  -d '{
    "httpRequest": {
      "method": "GET",
      "path": "/inventory/sku-42"
    },
    "httpResponse": {
      "statusCode": 200,
      "headers": {"Content-Type": ["application/json"]},
      "body": "{\"sku\":\"sku-42\",\"available\":7}"
    }
  }' | jq .

Verify the customer-facing behavior, not merely the control API response:

curl -fsS http://localhost:8080/inventory/sku-42 | jq -e '.sku == "sku-42" and .available == 7'
curl -fsS http://localhost:1080/inventory/sku-42 | jq -e '.sku == "sku-42" and .available == 7'

Both commands must print true. This small example also shows a style difference: WireMock can express a JSON response as jsonBody, while the MockServer REST expectation above supplies a JSON string plus an explicit content type.

Step 3: Compare JSON and Header Matching

A useful double must reject requests that violate client behavior without freezing irrelevant fields. For this order API, the tenant header, SKU, and quantity matter. A trace identifier is allowed but should not be part of the match.

WireMock's equalToJson matcher can ignore extra object elements while keeping the named values exact:

curl -fsS -X POST http://localhost:8080/__admin/mappings \
  -H 'Content-Type: application/json' \
  -d '{
    "name": "create order for tenant acme",
    "request": {
      "method": "POST",
      "urlPath": "/orders",
      "headers": {"X-Tenant": {"equalTo": "acme"}},
      "bodyPatterns": [{
        "equalToJson": {"sku": "sku-42", "quantity": 2},
        "ignoreExtraElements": true,
        "ignoreArrayOrder": true
      }]
    },
    "response": {
      "status": 201,
      "headers": {"Content-Type": "application/json"},
      "jsonBody": {"orderId": "ord-100", "status": "accepted"}
    }
  }' | jq -e '.id'

MockServer's JSON matcher calls the corresponding subset mode ONLY_MATCHING_FIELDS:

curl -fsS -X PUT http://localhost:1080/mockserver/expectation \
  -H 'Content-Type: application/json' \
  -d '{
    "httpRequest": {
      "method": "POST",
      "path": "/orders",
      "headers": {"X-Tenant": ["acme"]},
      "body": {
        "type": "JSON",
        "json": {"sku": "sku-42", "quantity": 2},
        "matchType": "ONLY_MATCHING_FIELDS"
      }
    },
    "httpResponse": {
      "statusCode": 201,
      "headers": {"Content-Type": ["application/json"]},
      "body": "{\"orderId\":\"ord-100\",\"status\":\"accepted\"}"
    }
  }' | jq .

Send the same request, including an extra field, to both servers:

for port in 8080 1080; do
  curl -fsS -X POST "http://localhost:${port}/orders" \
    -H 'Content-Type: application/json' \
    -H 'X-Tenant: acme' \
    -d '{"sku":"sku-42","quantity":2,"traceId":"test-789"}' \
    | jq -e '.status == "accepted"'
done

Expect true twice. If the client depends on the exact document, use strict matching instead. Tolerance should represent a deliberate compatibility rule. It should never be added simply to silence a mismatch. For broader payload strategy, see API test data management.

Step 4: Model One Failure Followed by Recovery

Retry tests need deterministic response order. WireMock represents the transition with a named scenario. The first mapping requires the initial Started state and moves the scenario to Recovered; the second mapping becomes eligible afterward.

curl -fsS -X POST http://localhost:8080/__admin/mappings \
  -H 'Content-Type: application/json' \
  -d '{
    "scenarioName": "shipping retry",
    "requiredScenarioState": "Started",
    "newScenarioState": "Recovered",
    "request": {"method": "GET", "urlPath": "/shipping/quote"},
    "response": {"status": 503, "jsonBody": {"error": "temporarily_unavailable"}}
  }' | jq -e '.id'

curl -fsS -X POST http://localhost:8080/__admin/mappings \
  -H 'Content-Type: application/json' \
  -d '{
    "scenarioName": "shipping retry",
    "requiredScenarioState": "Recovered",
    "request": {"method": "GET", "urlPath": "/shipping/quote"},
    "response": {"status": 200, "jsonBody": {"price": 12.5, "currency": "USD"}}
  }' | jq -e '.id'

MockServer can express the first response as a higher-priority expectation with one remaining match. An unlimited lower-priority expectation handles later calls.

curl -fsS -X PUT http://localhost:1080/mockserver/expectation \
  -H 'Content-Type: application/json' \
  -d '{
    "httpRequest": {"method": "GET", "path": "/shipping/quote"},
    "httpResponse": {"statusCode": 503, "body": "{\"error\":\"temporarily_unavailable\"}"},
    "times": {"remainingTimes": 1, "unlimited": false},
    "priority": 100
  }' | jq .

curl -fsS -X PUT http://localhost:1080/mockserver/expectation \
  -H 'Content-Type: application/json' \
  -d '{
    "httpRequest": {"method": "GET", "path": "/shipping/quote"},
    "httpResponse": {"statusCode": 200, "body": "{\"price\":12.5,\"currency\":\"USD\"}"},
    "priority": 10
  }' | jq .

Verify the sequence for each server by collecting only HTTP status codes:

for port in 8080 1080; do
  first=$(curl -sS -o /dev/null -w '%{http_code}' "http://localhost:${port}/shipping/quote")
  second=$(curl -sS -o /dev/null -w '%{http_code}' "http://localhost:${port}/shipping/quote")
  test "$first" = 503 && test "$second" = 200
done

A zero exit status proves both sequences. For real client coverage, assert maximum attempts, backoff policy, and the final error returned after exhaustion. Add the response variants from API error handling and negative testing instead of treating every non-200 result as equivalent.

Step 5: Verify Outbound Requests

Returning a successful stub response does not prove call count. A defect might send an order twice, retry a non-idempotent operation, or omit an interaction while another broad stub keeps the test green. Verification turns recorded traffic into an assertion.

WireMock's count endpoint accepts the same request-pattern shape used for stubbing. The order request was sent once in Step 3:

curl -fsS -X POST http://localhost:8080/__admin/requests/count \
  -H 'Content-Type: application/json' \
  -d '{"method":"POST","urlPath":"/orders","headers":{"X-Tenant":{"equalTo":"acme"}}}' \
  | jq -e '.count == 1'

MockServer's verification endpoint returns HTTP 202 when the count falls within the supplied bounds and 406 when it does not. Setting both bounds to one expresses exactly once:

status=$(curl -sS -o /dev/null -w '%{http_code}' \
  -X PUT http://localhost:1080/mockserver/verify \
  -H 'Content-Type: application/json' \
  -d '{
    "httpRequest":{"method":"POST","path":"/orders","headers":{"X-Tenant":["acme"]}},
    "times":{"atLeast":1,"atMost":1}
  }')
test "$status" = 202

Both verification commands should exit successfully. Clear recorded traffic immediately before the action under test when exact counts matter. In a parallel suite, include a per-test correlation header in the matcher so another worker cannot satisfy the assertion. Retry-sensitive writes also need the safeguards in API idempotency testing.

Step 6: Diagnose an Unmatched Request

Trigger a mismatch intentionally by sending the wrong tenant. Do not use -f here because a non-2xx response is the expected evidence.

for port in 8080 1080; do
  curl -sS -o /dev/null -w "port=${port} status=%{http_code}\n" \
    -X POST "http://localhost:${port}/orders" \
    -H 'Content-Type: application/json' \
    -H 'X-Tenant: other-company' \
    -d '{"sku":"sku-42","quantity":2}'
done

Expect a non-success status from each server because no expectation accepts that tenant. The exact default response representation is less important than the absence of a false match.

WireMock can list unmatched requests and rank the closest configured mappings:

curl -fsS http://localhost:8080/__admin/requests/unmatched \
  | jq -e '.requests | length >= 1'

curl -fsS http://localhost:8080/__admin/requests/unmatched/near-misses \
  | jq '.nearMisses[0].matchResult'

MockServer can retrieve recorded requests filtered by method and path. Read the returned headers to see the value the application actually sent:

curl -fsS -X PUT 'http://localhost:1080/mockserver/retrieve?type=REQUESTS' \
  -H 'Content-Type: application/json' \
  -d '{"method":"POST","path":"/orders"}' \
  | jq -e 'map(select((.headers["X-Tenant"] // .headers["x-tenant"]) == ["other-company"])) | length >= 1'

The verification prints true. Diagnose from recorded evidence before loosening a matcher. The wrong header may expose a product defect, a stale test fixture, or a casing assumption. A permissive wildcard would conceal all three. Preserve a correlation ID when several downstream calls overlap; correlating dynamic values in API tests shows how to keep those flows traceable.

Which Should You Choose for WireMock vs MockServer API Testing

Choose WireMock if most contributors work in Java, tests run inside JUnit, or product teams own small dependency doubles beside their services. Its mapping files are approachable for QA engineers who do not need to learn a large control-plane model. The unmatched-request and near-miss APIs are a decisive advantage when the common failure is "why did my stub not match?" WireMock also fits well when response templating and Java extensions need to stay close to a JVM codebase.

Choose MockServer if a shared virtualization service must support suites written in several languages, or if forwarding and traffic inspection are primary jobs. Expectation counts, priority, and time to live allow precise, temporary behavior. Retrieval of requests, paired exchanges, logs, generated formats, and HAR data helps when the server sits between a system under test and a real upstream. Its Java client remains strong, so choosing it does not require a polyglot stack.

Do not switch solely because one tool has a longer feature page. Prototype one high-risk interaction: authentication headers, a large JSON request, a transient error, one verification, and one deliberate mismatch. Compare configuration size, test isolation, failure output, startup model, and the effort to run it in CI. The deliberate failure is often more revealing than the happy path.

Some teams legitimately use both. A Java component suite might embed WireMock for fast isolated tests while a platform integration environment uses MockServer for proxying and shared traffic capture. That arrangement works only when ownership and scope are explicit. Avoid duplicating the same expectation catalog in two syntaxes because the copies will drift.

A short decision record should name the chosen topology, version policy, definition owner, test-data rules, parallel-isolation scheme, and artifact-retention policy. Include one rejected alternative and the concrete reason it lost. Revisit the choice when the suite changes language, becomes a shared service, adds proxy recording, or spends significant engineering time on mismatch triage. This keeps the tool decision tied to operating evidence instead of institutional habit.

CI, Persistence, and Team Ownership

Commit stable WireMock mappings or MockServer initialization JSON when the behavior is reusable and reviewable. Generate per-test expectations through the control API when unique data, parallel isolation, or short lifetimes matter. Never seed the catalog by copying production payloads without removing credentials, tokens, personal data, and volatile headers.

In CI, pin container versions, wait on a health endpoint, load definitions, run tests, export diagnostics on failure, and destroy the container. Treat logs and recorded requests as test artifacts, but redact them before long-term storage. Disable or bound request journals in load-oriented suites if memory growth becomes relevant.

Keep one owner for shared behavior and a clear reset policy. A pipeline that reuses a long-lived server needs namespaces, expectation expiration, and cleanup after interrupted jobs. An ephemeral server per job is simpler and usually safer. Run actual latency and throughput checks with a purpose-built workflow such as the API performance testing tutorial; mock response speed says nothing about provider capacity.

Common Mistakes

  • Matching only the path. A test can accept the wrong method, tenant, media type, or payload and still look green.
  • Matching every generated value exactly. Timestamps, trace IDs, and random identifiers make definitions brittle when they are not contract behavior.
  • Verifying the response but not the outbound call. Duplicate writes and excessive retries remain invisible without request-count assertions.
  • Reusing journals across tests. An old request can satisfy a new verification, especially with broad matchers.
  • Using fixed sleeps for startup or asynchronous calls. Poll a health endpoint or use an eventual verification with a bounded timeout.
  • Giving multiple definitions the same match and accidental precedence. Use explicit priority, scenario state, or unique predicates, then test which response wins.
  • Recording production traffic and committing it unchanged. Captures can contain secrets, personal information, unstable headers, and examples that are too specific.
  • Treating the mock as a provider oracle. A response you authored cannot prove compatibility, persistence, authorization, or deployed routing.
  • Sharing one mutable server across parallel workers without correlation. One worker can consume another worker's one-time expectation or inflate its count.
  • Returning only happy paths. Clients need deterministic coverage for validation, authentication, conflict, throttling, timeout, and unavailable responses.

The last point should be risk-driven. Start with errors that change client control flow, then expand using API security testing basics. Do not simulate a security check and conclude the real provider enforces it.

Troubleshooting

Container exits immediately -> Run docker logs for the named container, confirm the image tag exists, and check that the published port is free. Keep the explicit MockServer -serverPort 1080 argument paired with port 1080.

The endpoint returns 404 after configuration -> Retrieve active mappings or expectations and confirm the control request succeeded. Then compare the recorded method, path, query, headers, and body with every matcher. In WireMock, inspect near misses before changing the mapping.

A JSON body that looks identical does not match -> Check whether one side is sending JSON as a quoted string, whether numeric and string types differ, and whether array order is meaningful. Use semantic JSON matching and choose strict or subset mode intentionally.

The retry sequence starts in the recovered state -> Reset WireMock scenarios and MockServer expectations before the test. A shared long-lived instance retains state until explicitly reset or replaced.

Exact verification passes locally but fails in parallel CI -> Give each test a unique correlation header and include it in both expectation and verification. Alternatively, run one isolated container per worker or job.

Diagnostics expose credentials -> Redact authorization, cookies, API keys, and personal payload fields before uploading artifacts. Prefer synthetic test data and short-lived tokens even in non-production environments.

Interview Questions and Answers

The structured interview set accompanying this article covers tool selection, stubbing versus verification, isolation, JSON matching, retry testing, residual risk, and migration evidence. Strong answers connect a product risk to a specific server feature and then name what still requires a real provider. Memorizing feature lists is less convincing than explaining how you would isolate a test, seed a controlled failure, and diagnose a mismatch from recorded traffic.

Where To Go Next

Stop the lab containers when you finish:

docker stop qajobfit-wiremock qajobfit-mockserver

Verify cleanup with docker ps --filter name=qajobfit-wiremock --filter name=qajobfit-mockserver; it should print only the header. Because the containers use --rm, Docker removes them after stopping.

Next, implement the chosen server behind your real API client and preserve the same three proof points: a successful response, a controlled failure sequence, and an outbound-request verification. Then add provider compatibility with the contract testing guide. If you are preparing for an SDET role, use /practice to rehearse why mocks complement rather than replace integration tests.

Conclusion

WireMock is the stronger default for Java-centric component tests, straightforward file mappings, and fast near-miss diagnosis. MockServer is the stronger choice for advanced expectation lifecycle controls, proxy-centered service virtualization, polyglot clients, and detailed traffic retrieval.

Run the representative lab before standardizing. Choose the tool whose configuration expresses your dependency risks clearly and whose failure evidence gets an engineer to the cause quickly. Whichever server you adopt, isolate state, verify outbound calls, protect captured data, and keep real-provider checks elsewhere in the test strategy.

Interview Questions and Answers

How would you compare WireMock and MockServer in an automation framework design review?

I would compare execution style, matching needs, response behavior, verification, diagnostics, persistence, proxying, TLS, and team language support. WireMock is especially natural inside Java and JUnit or as a file-driven standalone server. MockServer deserves extra weight for bounded and prioritized expectations, proxy workflows, and rich traffic retrieval. I would prove the decision with one representative dependency rather than a feature checklist alone.

What is the difference between stubbing a response and verifying a request?

Stubbing defines what the double returns when a request matches. Verification inspects recorded traffic and asserts that the system under test actually sent the expected interaction. A response assertion can pass while the wrong number of calls occurred, so important side effects and retry behavior need explicit verification.

How do you prevent mock-server tests from influencing one another?

Give each test unique paths or correlation headers, then reset both expectations and recorded requests at a controlled boundary. Avoid sharing one mutable server across parallel tests unless traffic is namespaced. If a suite must share a process, generate identifiers per test and verify with those identifiers instead of broad path-only matchers.

When should a request-body matcher ignore extra JSON fields?

Ignore extras when the client cares about a stable subset and additional fields are valid evolution. Use strict matching when the complete payload is itself the behavior, such as a signed message or tightly governed command. The matcher should encode a compatibility decision, not merely make a failing test green.

How would you test a client's retry policy with WireMock or MockServer?

Configure one transient failure followed by a success, call the real client, and assert both the final client result and the exact request count. Also test a permanent failure to prove retries stop at the configured limit. Keep the delay small or use a controllable clock in client code so the suite remains fast.

What risks remain after all tests pass against a mock API?

The provider may have changed its contract, authentication may differ, network policy may block the route, production data may expose unmodeled cases, and real latency or capacity may be unacceptable. Mocks also cannot prove provider persistence or downstream effects. Contract, integration, security, resilience, and performance tests cover those separate risks.

What evidence would make you replace one mock server with the other?

I would look for recurring friction tied to a tool capability, such as unreadable mismatch triage, missing proxy controls, difficult language integration, or brittle state sequencing. Then I would port a small set of representative tests and compare setup code, failure output, runtime, and CI maintenance. Migration is justified by lower operational cost or better risk coverage, not preference alone.

Frequently Asked Questions

Is WireMock better than MockServer for API testing?

WireMock is usually the easier fit for Java and JUnit test suites, especially when readable JSON mappings and near-miss diagnostics matter. MockServer can be the better platform for proxy-heavy service virtualization, bounded expectations, and teams using several client languages. The better tool is the one that matches your execution model and debugging needs.

Can WireMock and MockServer run without Java test code?

Yes. Both provide Docker images and HTTP control APIs, so tests written in any language can configure them. The examples in this guide use curl to make that portability explicit.

Do WireMock and MockServer support request verification?

Both record requests and can verify that a matcher was observed. WireMock exposes request-count and journal endpoints, while MockServer's verify endpoint supports count constraints and ordered sequences. Reset the journal or logs before each isolated test to avoid counting earlier traffic.

Which tool is better for simulating retries and transient API failures?

WireMock scenarios provide named state transitions that are easy to read when modeling a sequence. MockServer can limit an expectation with `times`, combine it with priorities, and apply a time to live. Either can model a first-call failure followed by recovery, but the configuration style differs.

Can these mock servers replace contract testing?

No. A mock proves how your client behaves against the response you configured, not that a real provider still honors that behavior. Add consumer-driven or schema contract checks when compatibility between independently changing services is a release risk.

How should mock expectations be stored in a repository?

Keep stable expectations as reviewed JSON files beside the tests or in a dedicated service-virtualization module. Pin the server image, give each mapping a behavioral name, and avoid copying secrets or real customer data from recordings. Generate short-lived test-specific expectations at runtime when isolation is more important than reuse.

Why does a configured mock return 404?

The incoming request did not satisfy every configured predicate, or the expected mapping was never loaded. Compare method, path, query, headers, body type, and reset timing. WireMock near misses and MockServer recorded-request retrieval expose the actual request so you can find the mismatch.

Related Guides