QA How-To
Hoverfly vs WireMock Service Virtualization (2026)
Compare Hoverfly vs WireMock service virtualization with runnable Docker examples for matching, capture, state, diagnostics, CI, and a clear 2026 verdict.
19 min read | 3,579 words
TL;DR
Hoverfly is the stronger choice for proxy-first capture and reusable, language-neutral service simulations. WireMock is the safer default for Java-heavy test suites and teams that prioritize a compact stubbing DSL, JUnit lifecycle support, scenarios, extensions, and near-miss diagnostics. Both can run as standalone containers, match HTTP requests, replay controlled responses, add latency, record traffic, and expose request journals.
Key Takeaways
- Choose Hoverfly when transparent proxy capture, portable simulations, traffic shaping, and language-neutral virtualization define the job.
- Choose WireMock for Java and JUnit ergonomics, concise hand-authored stubs, scenarios, extensions, and excellent mismatch diagnostics.
- Run Hoverfly as a proxy when capturing traffic and as a webserver when an application can accept an injected base URL.
- Match only contract-significant request data so a test catches client defects without breaking on harmless representation changes.
- Verify outbound requests separately from asserting the response returned by a virtual service.
- Pin container versions, isolate state and journals per test job, and publish diagnostics when CI fails.
- Keep contract and targeted real-integration tests because neither virtual service proves current provider behavior.
Hoverfly vs WireMock service virtualization is a choice between two capable HTTP test doubles with different centers of gravity. Pick Hoverfly when you want a lightweight proxy to capture real traffic, turn it into portable simulations, and apply behavior across polyglot systems. Pick WireMock when Java integration, hand-authored stubs, JUnit lifecycle control, response templating, scenarios, and direct mismatch diagnostics are more important.
Do not choose from a feature checklist alone. The decisive questions are how traffic reaches the double, whether engineers author or record behavior, who owns its lifecycle, and how quickly a failed match can be diagnosed. This guide runs the same inventory API through both products, then exercises strict matching, state, latency, recording, journals, and CI design.
Neither product replaces provider verification. A simulation proves how your consumer behaves against the model you supplied. Pair it with API contract testing with Pact or another provider-backed check when compatibility matters.
TL;DR
WireMock is the default recommendation for a JVM team. Its Java DSL and JUnit 5 support make server startup, dynamic ports, reset behavior, stubbing, and verification part of the test fixture. Its standalone JSON mappings are also easy to read, and unmatched-request plus near-miss tooling gives failures a short path to explanation.
Hoverfly wins when the virtual service should behave like a reusable network capability. Its proxy mode captures HTTP and HTTPS conversations without changing an application's base URLs, while webserver mode supports direct endpoint substitution. Simulation files, native delays, state, journaling, and middleware make it useful beyond a single test framework.
| Decision area | Hoverfly | WireMock | Better fit |
|---|---|---|---|
| Primary model | API simulation through proxy or webserver | HTTP stubs through embedded server or standalone process | Depends on topology |
| Capture workflow | Core capture mode, destination filters, stateful sequences | Record and playback through proxy mappings or recorder APIs | Hoverfly for proxy-first capture |
| Java tests | Native Java binding available | Mature Java DSL and JUnit Jupiter integration | WireMock |
| Polyglot teams | REST API, hoverctl, simulation JSON | REST admin API, JSON mappings, ecosystem clients | Slight Hoverfly edge when simulations are shared |
| Matching | Exact, glob, regex, JSON, JSON partial, JSONPath, XPath, and strongest-match scoring | Rich URL, header, JSON, JSONPath, XPath, schema, multipart, and custom matching | WireMock for breadth, Hoverfly for score-based selection |
| Stateful behavior | Explicit state and recorded sequences | Named scenarios and state transitions | Tie for short workflows |
| Latency and faults | Fixed or log-normal delays, middleware, synthesize and modify modes | Fixed or distributed delays, faults, transformers, extensions | Depends on failure model |
| Diagnosis | Journal, logs, cache, state, and closest-miss data | Request journal, unmatched requests, near misses, serve events | WireMock for matcher triage |
| Deployment footprint | Single Go binary or container | Java process, JAR, container, or embedded library | Hoverfly for a small standalone runtime |
What You Will Build
You will run two local virtual services and compare equivalent behavior:
- Hoverfly v1.12.10 in webserver mode on port 8500, with its admin API on 8888.
- WireMock 3.13.2 on port 8080.
- A GET inventory response and a contract-aware POST order matcher.
- A 503 then 200 shipping sequence with deterministic latency.
- A Hoverfly proxy capture whose upstream provider is the WireMock container.
- Journal and unmatched-request checks that can become CI assertions.
Every step has a verification command. The examples use REST control APIs so the comparison remains useful to Java, JavaScript, Python, .NET, and Go teams.
Prerequisites
Install Docker Engine or Docker Desktop, curl, and jq. Use the pinned images shown here instead of floating tags. Hoverfly v1.12.10 is the current stable documentation line used by this lab. WireMock 3.13.2 is the stable 3.x line; WireMock 4 is still a beta line and should be evaluated separately before a production migration.
Check the local tools:
docker version --format '{{.Server.Version}}'
curl --version | head -n 1
jq --version
Each command must print a version, and Docker must report a server version. The tutorial needs ports 8080, 8500, and 8888. Stop any local process already using them. The Docker commands use a dedicated network so Hoverfly can capture calls to WireMock without relying on host-specific container DNS aliases.
Step 1: Start the Hoverfly vs WireMock Service Virtualization Lab
Create a network, then start both containers with stable names. Hoverfly runs as a webserver for the hand-authored examples, so clients call port 8500 directly. Port 8888 remains the control plane.
docker network inspect qajobfit-virtualization >/dev/null 2>&1 || \
docker network create qajobfit-virtualization
docker run --rm -d \
--name qajobfit-hoverfly \
--network qajobfit-virtualization \
-p 8500:8500 -p 8888:8888 \
spectolabs/hoverfly:v1.12.10 \
-webserver
docker run --rm -d \
--name qajobfit-wiremock \
--network qajobfit-virtualization \
-p 8080:8080 \
wiremock/wiremock:3.13.2
Do not add a sleep with a guessed duration to CI. Poll the real administration endpoints so a slow runner gets time to start and a broken container fails visibly.
Verify both servers:
for attempt in 1 2 3 4 5 6 7 8 9 10; do
curl -fsS http://localhost:8888/api/v2/hoverfly/version >/dev/null && break
sleep 1
done
curl -fsS http://localhost:8888/api/v2/hoverfly/version \
| jq -e '.version == "v1.12.10"'
curl -fsS http://localhost:8080/__admin/mappings \
| jq -e '.mappings | type == "array"'
Both jq expressions should print true. If they fail, inspect docker logs for the named container. A healthy admin endpoint proves startup, not that a customer endpoint has been configured.
Step 2: Create Equivalent Inventory Responses
The same stub in each tool. WireMock uses a Java DSL or JSON mapping; Hoverfly uses a simulation file.
// WireMock: stub the inventory endpoint (Java DSL)
stubFor(get(urlEqualTo("/inventory/SKU-1"))
.willReturn(okJson("{\"sku\":\"SKU-1\",\"available\":42}")));
// Hoverfly: equivalent simulation.json, imported with `hoverctl import simulation.json`
{
"data": { "pairs": [{
"request": {
"path": [{ "matcher": "exact", "value": "/inventory/SKU-1" }],
"method": [{ "matcher": "exact", "value": "GET" }]
},
"response": {
"status": 200,
"body": "{\"sku\":\"SKU-1\",\"available\":42}",
"headers": { "Content-Type": ["application/json"] }
}
}] },
"meta": { "schemaVersion": "v5.2" }
}
Verify: curl localhost:8080/inventory/SKU-1 against each tool returns the same 200 body, proving the stubs are equivalent before you compare behavior.
Load a complete Hoverfly simulation through PUT. PUT replaces existing simulation data, which makes the starting state deterministic. The request intentionally matches method and path only because webserver mode removes the original provider destination.
curl -fsS -X PUT http://localhost:8888/api/v2/simulation \
-H 'Content-Type: application/json' \
-d '{
"data": {
"pairs": [{
"request": {
"path": [{"matcher": "exact", "value": "/inventory/sku-42"}],
"method": [{"matcher": "exact", "value": "GET"}]
},
"response": {
"status": 200,
"body": "{\"sku\":\"sku-42\",\"available\":7}",
"encodedBody": false,
"headers": {"Content-Type": ["application/json"]},
"templated": false
}
}],
"globalActions": {"delays": [], "delaysLogNormal": []}
},
"meta": {"schemaVersion": "v5.2"}
}' >/dev/null
Create the equivalent WireMock mapping. WireMock uses a response jsonBody, so the administration payload stays readable without embedding a serialized JSON document.
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 | type == "string"'
Verify the behavior through the service ports, not merely the control API responses:
curl -fsS http://localhost:8500/inventory/sku-42 \
| jq -e '.sku == "sku-42" and .available == 7'
curl -fsS http://localhost:8080/inventory/sku-42 \
| jq -e '.sku == "sku-42" and .available == 7'
Expect true twice. The visible configuration difference is meaningful: Hoverfly's exported artifact represents a broader simulation document, while a WireMock mapping represents one independently manageable stub.
Step 3: Compare Contract-Aware Request Matching
A useful virtual service rejects a broken consumer request without coupling to irrelevant fields. For POST /orders, method, path, tenant header, SKU, and quantity define the behavior. A traceId may vary and should not prevent a match.
Append a Hoverfly pair with a JSON partial body matcher. POST appends unique pairs instead of replacing the inventory simulation.
curl -fsS -X POST http://localhost:8888/api/v2/simulation \
-H 'Content-Type: application/json' \
-d '{
"data": {
"pairs": [{
"request": {
"path": [{"matcher": "exact", "value": "/orders"}],
"method": [{"matcher": "exact", "value": "POST"}],
"headers": {
"X-Tenant": [{"matcher": "exact", "value": "acme"}]
},
"body": [{
"matcher": "jsonPartial",
"value": "{\"sku\":\"sku-42\",\"quantity\":2}"
}]
},
"response": {
"status": 201,
"body": "{\"orderId\":\"ord-100\",\"status\":\"accepted\"}",
"encodedBody": false,
"headers": {"Content-Type": ["application/json"]},
"templated": false
}
}],
"globalActions": {"delays": [], "delaysLogNormal": []}
},
"meta": {"schemaVersion": "v5.2"}
}' >/dev/null
WireMock expresses the same tolerance through equalToJson with ignoreExtraElements. Array-order tolerance is irrelevant to this payload, so it is not enabled casually.
curl -fsS -X POST http://localhost:8080/__admin/mappings \
-H 'Content-Type: application/json' \
-d '{
"name": "accept order for tenant acme",
"request": {
"method": "POST",
"urlPath": "/orders",
"headers": {"X-Tenant": {"equalTo": "acme"}},
"bodyPatterns": [{
"equalToJson": {"sku": "sku-42", "quantity": 2},
"ignoreExtraElements": true
}]
},
"response": {
"status": 201,
"headers": {"Content-Type": "application/json"},
"jsonBody": {"orderId": "ord-100", "status": "accepted"}
}
}' | jq -e '.id | type == "string"'
Send identical requests, including the intentionally unmodeled traceId:
for port in 8500 8080; 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. Now change X-Tenant to another value and both products should return a non-success result. Strong matching does not mean matching every browser or proxy header. It means encoding the smallest set of facts whose absence would constitute a client defect. The WireMock stubbing guide covers additional JSONPath, priority, and body-matching patterns.
Step 4: Model Failure, Delay, and Recovery
Fault injection is where the tools diverge. WireMock attaches faults per stub; Hoverfly applies delays as global actions.
// WireMock: a 503 with a 2s fixed delay on the same endpoint
stubFor(get(urlEqualTo("/inventory/SKU-1"))
.willReturn(aResponse().withStatus(503).withFixedDelay(2000)));
// Hoverfly: a 2s delay for all matching inventory GETs (globalActions)
{ "data": { "globalActions": { "delays": [
{ "urlPattern": "/inventory/.*", "httpMethod": "GET", "delay": 2000 }
] } } }
Verify: assert your client surfaces a timeout or retry after 2s under each tool, so the resilience test is provably driven by the virtualized fault, not luck.
Retry behavior needs deterministic state. Hoverfly supports state variables and treats keys prefixed with sequence: as ordered response sequences. Load a focused shipping simulation that returns 503 with a 250 ms delay, transitions state, then returns 200. PUT intentionally replaces the earlier Hoverfly pairs for this isolated experiment.
curl -fsS -X PUT http://localhost:8888/api/v2/simulation \
-H 'Content-Type: application/json' \
-d '{
"data": {
"pairs": [
{
"request": {
"path": [{"matcher": "exact", "value": "/shipping/quote"}],
"method": [{"matcher": "exact", "value": "GET"}],
"requiresState": {"sequence:shipping": "1"}
},
"response": {
"status": 503,
"body": "{\"error\":\"temporarily_unavailable\"}",
"encodedBody": false,
"headers": {"Content-Type": ["application/json"]},
"templated": false,
"fixedDelay": 250,
"transitionsState": {"sequence:shipping": "2"}
}
},
{
"request": {
"path": [{"matcher": "exact", "value": "/shipping/quote"}],
"method": [{"matcher": "exact", "value": "GET"}],
"requiresState": {"sequence:shipping": "2"}
},
"response": {
"status": 200,
"body": "{\"price\":12.5,\"currency\":\"USD\"}",
"encodedBody": false,
"headers": {"Content-Type": ["application/json"]},
"templated": false
}
}
],
"globalActions": {"delays": [], "delaysLogNormal": []}
},
"meta": {"schemaVersion": "v5.2"}
}' >/dev/null
WireMock represents the transition with a named scenario. Started is the built-in initial state.
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,
"fixedDelayMilliseconds": 250,
"jsonBody": {"error": "temporarily_unavailable"}
}
}' >/dev/null
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"}
}
}' >/dev/null
Verify response order for both virtual services:
for port in 8500 8080; 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. A real resilience test should also assert attempt limits, idempotency data, backoff bounds, timeout classification, and the final client-visible result. Add distinct 400, 401, 409, 429, malformed-body, connection, and exhaustion cases based on the REST Assured tutorial for beginners or your chosen API client rather than treating every failure as retryable.
Step 5: Capture and Replay Through Hoverfly
Capture is where Hoverfly's architecture becomes most distinct. Webserver mode cannot capture, so replace that container with standard proxy mode. The WireMock container now acts as a deterministic upstream provider on the shared Docker network.
docker rm -f qajobfit-hoverfly
docker run --rm -d \
--name qajobfit-hoverfly \
--network qajobfit-virtualization \
-p 8500:8500 -p 8888:8888 \
spectolabs/hoverfly:v1.12.10
for attempt in 1 2 3 4 5 6 7 8 9 10; do
curl -fsS http://localhost:8888/api/v2/hoverfly/version >/dev/null && break
sleep 1
done
curl -fsS -X PUT http://localhost:8888/api/v2/hoverfly/mode \
-H 'Content-Type: application/json' \
-d '{
"mode": "capture",
"arguments": {
"headersWhitelist": ["Content-Type"],
"stateful": false,
"overwriteDuplicate": false
}
}' >/dev/null
Call WireMock through Hoverfly's proxy port. Because the URL is sent to the proxy, Hoverfly resolves the upstream container name inside the Docker network.
curl -fsS --proxy http://localhost:8500 \
http://qajobfit-wiremock:8080/inventory/sku-42 \
| jq -e '.available == 7'
curl -fsS -X PUT http://localhost:8888/api/v2/hoverfly/mode \
-H 'Content-Type: application/json' \
-d '{"mode":"simulate","arguments":{"matchingStrategy":"strongest"}}' \
>/dev/null
The first call reaches WireMock and records the exchange. The mode change disconnects later behavior from that upstream response. Verify replay and inspect the captured pair:
curl -fsS --proxy http://localhost:8500 \
http://qajobfit-wiremock:8080/inventory/sku-42 \
| jq -e '.sku == "sku-42" and .available == 7'
curl -fsS http://localhost:8888/api/v2/simulation \
| jq -e '[.data.pairs[] | select(
.request.path[0].value == "/inventory/sku-42"
)] | length == 1'
Both checks should print true. Treat captured output as a draft, not a permanent truth. Remove volatile headers, redact tokens and personal data, replace brittle exact values with intentional matchers, and record provider-version context. WireMock also supports recording and snapshotting after proxy configuration, but its strongest everyday workflow remains explicitly authored mappings near tests. Guidance on preventing mock contract drift applies to recordings from either tool.
Step 6: Verify Calls and Diagnose Mismatches
A successful response assertion does not prove the consumer sent the call once. Hoverfly exposes a journal containing request-response exchanges, mode, timestamp, and latency. WireMock exposes count verification, received requests, unmatched requests, and near misses.
The inventory request reached Hoverfly twice in Step 5, once during capture and once during simulate. It reached WireMock at least twice across the lab, once directly in Step 2 and once as the capture upstream.
curl -fsS http://localhost:8888/api/v2/journal \
| jq -e '[.journal[] | select(
.request.path == "/inventory/sku-42"
)] | length == 2'
curl -fsS -X POST http://localhost:8080/__admin/requests/count \
-H 'Content-Type: application/json' \
-d '{"method":"GET","urlPath":"/inventory/sku-42"}' \
| jq -e '.count >= 2'
Expect true twice. Exact counts are safer when you clear the journal immediately before the action under test. In parallel CI, add a unique correlation header and include it in the journal filter so another worker cannot satisfy the assertion.
Now create one deliberate mismatch in each tool:
hoverfly_status=$(curl -sS --proxy http://localhost:8500 \
-o /dev/null -w '%{http_code}' \
http://qajobfit-wiremock:8080/inventory/missing)
wiremock_status=$(curl -sS -o /dev/null -w '%{http_code}' \
http://localhost:8080/inventory/missing)
test "$hoverfly_status" = 502
test "$wiremock_status" = 404
curl -fsS http://localhost:8888/api/v2/journal \
| jq -e '[.journal[] | select(
.request.path == "/inventory/missing" and .response.status == 502
)] | length >= 1'
curl -fsS http://localhost:8080/__admin/requests/unmatched \
| jq -e '[.requests[] | select(
.url == "/inventory/missing"
)] | length >= 1'
This illustrates the practical diagnostic difference. Hoverfly's journal is a broad traffic record suited to simulation analysis. WireMock makes unmatched traffic a direct testing concept and can rank near misses against configured mappings. If developers lose minutes reconstructing why a request failed to match, the richer WireMock failure path can outweigh a feature Hoverfly has elsewhere.
Hoverfly vs WireMock Service Virtualization in CI
Run either product as test-owned infrastructure. Give every job its own instance, simulation or mappings, state, journal, and network namespace. Pin image digests for high-control environments, poll readiness, clear state before the action, collect control-plane output on failure, and remove the instance in an unconditional cleanup phase.
Do not expose administration ports beyond the test network. Both control planes can change responses and reveal captured request data. Scrub Authorization, Cookie, Set-Cookie, API keys, customer identifiers, and regulated payload fields before committing recordings. Keep secrets out of diagnostic artifacts as carefully as production logs.
Which Should You Choose
Choose Hoverfly when the team starts with observed provider traffic and needs to distribute a reusable simulation across several languages. It is especially persuasive when transparent proxying avoids consumer configuration changes, when native delays or modify and synthesize modes are central, or when a compact standalone runtime belongs in shared development and test environments.
Choose WireMock when engineers primarily author behavior from contracts and examples. It is the better default for Spring, Java, Kotlin, or JUnit suites, for tests that want in-process lifecycle control, and for teams that value mapping readability, scenarios, response templating, extensions, and actionable near-miss messages.
Choose neither when the risk requires the real system. OAuth redirects, certificate trust, gateway policy, provider persistence, production quotas, and infrastructure routing may need a sandbox or deployed integration. Event-driven dependencies also need broker and schema behavior beyond HTTP; use the event-driven microservices testing guide for those boundaries.
Decision Checklist
Use this order instead of scoring dozens of features equally:
- Decide whether the consumer will use an explicit base URL or a forward proxy.
- Decide whether behavior begins as recorded traffic or hand-authored contract examples.
- List required matching, state, latency, templating, HTTPS, and verification behavior.
- Run one failure-heavy provider workflow in both tools.
- Review the generated artifact and the failure output with the engineers who will maintain it.
- Test parallel isolation and cleanup on the actual CI platform.
- Keep the tool whose ordinary path needs less custom glue.
The right choice is the one that makes a realistic model easy to review and a broken interaction easy to diagnose. A rare feature that never appears in your dependency risks should not decide the platform.
Troubleshooting
Hoverfly returns 502 in simulate mode -> The incoming request did not match a simulation pair. Check whether Hoverfly is acting as a proxy or webserver, then inspect path, destination, scheme, query, headers, body matchers, required state, and the journal entry. Do not weaken every matcher until the test turns green.
WireMock returns 404 -> No stub matched unless a catch-all mapping changed the fallback. Query unmatched requests and near misses, compare URL path versus full URL matching, inspect JSON semantics and required headers, then check mapping priority for overlaps.
Hoverfly capture mode is rejected -> You started Hoverfly with -webserver. Restart it in proxy mode, configure the client to use port 8500 as an HTTP proxy, trust the Hoverfly certificate for intercepted HTTPS, and switch back to simulate after capture.
The second run starts in the wrong state -> State survived the first test. Reset WireMock scenarios or the whole server, and delete or replace Hoverfly state before each case. Do not share scenario names or sequence keys across concurrent tests.
Captured simulations change on every recording -> Volatile headers, query values, timestamps, generated identifiers, or response data remain in the artifact. Filter capture headers, sanitize data, introduce semantic matchers, and review the diff rather than accepting regenerated files blindly.
Local tests pass but CI cannot connect -> A fixed port is occupied, a container is not ready, or localhost refers to a different network namespace. Publish the correct port, use container DNS inside shared networks, poll the admin endpoint, and attach container logs when readiness fails.
Interview Questions and Answers
Use the structured interviewQnA set below to rehearse architecture, matcher strictness, retries, recording risk, parallel isolation, and residual integration risk. A credible answer connects a dependency failure to a concrete control, explains how the test is reset and verified, and states what the virtual service cannot prove about the live provider.
Common Mistakes
- Choosing from the longest feature list without testing the team's hardest dependency workflow.
- Calling Hoverfly capture while it is running as a webserver, where capture is intentionally unavailable.
- Treating recorded Authorization headers, cookies, customer data, or timestamps as commit-ready fixtures.
- Matching every header and raw JSON byte, which turns harmless variation into unrelated failures.
- Using a broad success fallback that lets wrong paths, methods, tenants, or bodies pass.
- Sharing stateful sequences and request journals between parallel cases.
- Checking only the virtual response while never verifying the consumer's outbound interaction.
- Retrying POST or payment calls without a stable idempotency mechanism.
- Depending on latest container tags and discovering an upgrade inside an unrelated CI run.
- Calling a mocked browser-to-database path end to end after its critical provider was replaced.
- Building provider business logic inside templates or middleware until the fake needs its own test suite.
- Letting simulations drift because no contract or selected real-provider check challenges them.
Where To Go Next
Start with one external HTTP dependency and one risky client behavior, such as a retryable shipping quote or an idempotent payment request. Implement it in both products, force a mismatch, and compare what the next engineer sees in CI. The failing case is often more informative than the happy path.
Use WireMock stubbing patterns when the Java-oriented option wins. Add Pact API contract testing when a consumer-owned double needs provider verification. Apply Testcontainers integration testing to give each job a disposable runtime.
After the lab, remove the containers and network:
docker rm -f qajobfit-hoverfly qajobfit-wiremock
docker network rm qajobfit-virtualization
Verify cleanup with docker ps -a and docker network ls; neither qajobfit resource name should appear.
Conclusion
Hoverfly is the better service virtualization platform when proxy capture, portable simulations, network-level reuse, and a small standalone runtime drive the design. WireMock is the better default when JVM ergonomics, concise authored mappings, scenarios, extensions, and match diagnostics drive daily testing.
Run the same failure-heavy dependency through both before standardizing. Then preserve confidence with isolated state, verified interactions, protected recordings, pinned versions, and provider-backed contract or integration coverage.
Interview Questions and Answers
What architectural difference matters most between Hoverfly and WireMock?
Hoverfly centers on reusable simulations served through a forward proxy or direct webserver. WireMock centers on request-to-response stub mappings that can run embedded in JVM tests or as a standalone server. I choose after deciding topology and ownership, not by counting matchers.
How would you choose request matching strictness in a virtual service?
I match the method, path, required identity or tenancy data, meaningful query values, and semantic body fields that form the consumer contract. I ignore transport noise and optional fields unless they change provider behavior. The matcher must catch a real client defect without coupling the test to incidental representation.
How do you test retries with Hoverfly or WireMock?
I configure a deterministic first failure followed by success, then assert the maximum attempts, final result, idempotency information, and reasonable backoff bounds. Separate cases cover retry exhaustion and nonretryable responses. Hoverfly sequences or WireMock scenarios can drive the transition.
What risks come with record and replay?
Recordings can contain secrets, personal data, volatile headers, obsolete schemas, and accidental provider behavior. I sanitize them, replace unstable exact matches with intentional rules, record provenance, review changes, and keep provider-backed checks. Capture accelerates authoring but does not create an authoritative contract.
How would you make service virtualization safe for parallel CI?
I allocate one instance per worker with unique ports or container networking and load only that worker's definitions. I reset state and journals at test boundaries, use unique scenario keys, and capture diagnostics before cleanup. Correlation headers help filtering, but process isolation is more reliable.
When would you reject both Hoverfly and WireMock?
I would reject a double when the test's purpose is to prove real gateway policy, TLS trust, provider persistence, live credentials, quota enforcement, or deployed routing. I would also avoid HTTP virtualization for a broker protocol that needs real delivery semantics. The replacement must not remove the risk named by the test.
How do you prevent a virtual service from drifting?
I version definitions with the consumer, validate simulation or mapping schemas, link examples to contract versions, and run provider verification or selected sandbox checks. Production incidents and provider changes feed reviewed updates rather than silent fixture regeneration. Every shared simulation also needs an explicit owner.
Frequently Asked Questions
Is Hoverfly better than WireMock for service virtualization?
Hoverfly is better when proxy capture, portable simulations, traffic shaping, and language-neutral deployment are the main needs. WireMock is better for JVM-centric suites, hand-authored stubs, JUnit lifecycle control, extensions, and detailed mismatch investigation.
Can Hoverfly and WireMock both record API traffic?
Yes. Hoverfly records through capture mode while acting as a proxy, and it can preserve stateful response sequences. WireMock supports record and playback plus snapshotting after requests have passed through proxy mappings.
Does Hoverfly work as a normal mock web server?
Yes. Start it in webserver mode and point the application's dependency base URL at port 8500. Webserver mode can simulate or synthesize behavior, but it cannot capture upstream traffic.
Is WireMock limited to Java tests?
No. WireMock is implemented for the JVM and has excellent Java integration, but its standalone container, JSON mappings, and REST admin API work with any client language. Java remains its most natural embedded workflow.
Which tool has better request mismatch diagnostics?
WireMock usually provides the faster path for matcher triage through unmatched requests, near misses, serve events, and verification failures. Hoverfly provides journals, logs, cache information, and closest-miss data that are useful for broader simulation traffic analysis.
Can service virtualization replace API contract testing?
No. A virtual service validates consumer behavior against a controlled model, but that model can drift. Contract verification or targeted provider integration checks are still needed to establish compatibility with the real provider.
How should Hoverfly or WireMock run in CI?
Give each job an isolated process or container, pin its version, poll a readiness endpoint, load versioned behavior, reset state before each case, and save redacted diagnostics on failure. Never share a mutable journal or scenario state across parallel jobs.