QA How-To
WireMock stubbing: A Practical Guide (2026)
Master WireMock stubbing in 2026 with runnable Java examples for request matching, stateful scenarios, faults, verification, reusable mappings, and CI.
24 min read | 3,033 words
TL;DR
Good WireMock stubbing defines the smallest meaningful request match, returns a contract-accurate response, and verifies the consumer's important outbound behavior. Use dynamic ports, deterministic data, explicit priorities, stateful scenarios only where needed, and failure simulation that maps to a real resilience requirement.
Key Takeaways
- Match only request attributes that define the dependency contract, and avoid accidental coupling to irrelevant headers or JSON order.
- Use the WireMock JUnit Jupiter extension on a dynamic port so tests own lifecycle and run safely in parallel.
- Make specific mappings higher priority than catch-all behavior and fail visibly on unexpected traffic.
- Test latency, malformed responses, connection faults, and state transitions as deliberate consumer behaviors, not random chaos.
- Verify important outbound requests and side effects, but do not duplicate every stub matcher as a brittle interaction assertion.
- Keep reusable mappings versioned, reviewed, deterministic, and tied to a known provider contract.
- Prefer stable WireMock 3.x for production test suites in 2026 unless a WireMock 4 beta evaluation has explicit ownership.
WireMock stubbing lets a test replace an HTTP dependency with controlled behavior, but the value is not the canned response alone. A good stub captures the consumer's view of the provider contract, rejects important mistakes, returns realistic success and failure shapes, and gives precise evidence when the application sends something unexpected.
For a stable 2026 Java suite, WireMock 3.13.2 is the documented 3.x release, while WireMock 4 remains available as a beta line with breaking changes possible. This guide uses the stable 3.x Java API and JUnit Jupiter extension. Evaluate the beta separately if its modular architecture solves a defined problem, and never let an unpinned beta update enter CI by accident.
TL;DR
| Stubbing concern | Weak pattern | Strong pattern |
|---|---|---|
| URL | Match an entire dynamic URL string | Match path, then important query parameters |
| JSON body | Compare raw text and field order | Use semantic JSON or targeted JSONPath matchers |
| Response | Return only status 200 | Match provider status, headers, schema, and semantics |
| Priority | Depend on registration order silently | Set priority for overlapping mappings |
| State | Share mutable state across every test | Use scenarios only for required conversations |
| Failure | Add a random delay | Simulate a named timeout, fault, or malformed response |
| Verification | Assert every incidental header | Verify contract-critical request and count |
| Lifecycle | Run one fixed shared port | Use a test-owned dynamic port and reset mappings |
Use stubs to isolate a consumer, not to prove the provider works. Provider contract tests and a smaller number of real integration checks still matter.
1. WireMock Stubbing: What a Stub Should Prove
A stub mapping contains a request pattern and a response definition. When an incoming HTTP request matches, WireMock returns the configured response. Mappings can be created through the Java DSL, JSON files in a mappings directory, the admin HTTP API, or supported SDKs. This flexibility supports unit-like component tests, local development, integration environments, and reproducible fault cases.
The test subject should be the consumer. Suppose an order service calls a payment provider. WireMock can prove that the order service sends the required path, authorization form, idempotency key, content type, and payment fields. It can return approved, declined, invalid, throttled, delayed, or malformed provider responses. The test then asserts the order service's own state and API response.
WireMock does not prove the real provider currently accepts the request. A local stub can drift from the provider's OpenAPI document or production behavior. Control drift through consumer-driven contracts, provider schema validation, recorded examples reviewed against specifications, or targeted sandbox checks. The API contract testing with Pact guide explains when a contract broker adds value beyond local stubs.
Avoid overmatching. If the consumer contract does not care about User-Agent, a generated tracing header, or JSON object field order, do not require it. Overly strict stubs create false failures during harmless refactoring. Under-matching is also dangerous: a stub that matches any POST can hide a wrong path or missing account ID. Match the smallest set that distinguishes correct from incorrect consumer behavior.
2. Install WireMock and Use the JUnit Jupiter Extension
For Maven, add the stable WireMock library and a compatible JUnit Jupiter dependency under test scope. The versions below are reproducible examples for Java 17. Teams on the current JUnit 6 line should use dependency management and verify its Java baseline and build plugins.
<properties>
<maven.compiler.release>17</maven.compiler.release>
<junit.version>5.13.4</junit.version>
<wiremock.version>3.13.2</wiremock.version>
</properties>
<dependencies>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.wiremock</groupId>
<artifactId>wiremock</artifactId>
<version>${wiremock.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
@WireMockTest starts one server for the test class on a random HTTP port by default, configures the static DSL, resets stubs and requests before each test, and stops the server after the class. A test or lifecycle method can receive WireMockRuntimeInfo to read the base URL, port, or instance DSL. Dynamic ports avoid collisions on developer machines and parallel CI agents.
Fixed ports are appropriate only when the application under test cannot receive a dynamic dependency URL. Even then, allocate ports per worker or isolate jobs. Prefer injecting wmRuntimeInfo.getHttpBaseUrl() into the application configuration before starting the consumer. Do not hard-code localhost:8080 across a large suite.
If several dependencies need separate behavior, register multiple programmatic WireMockExtension instances with dynamic ports. Name them by domain, such as payments and inventory, and inject their URLs separately. One catch-all mock server for every external system can conceal routing mistakes.
3. A Complete Runnable WireMock Java Example
The following test uses the JUnit Jupiter extension, Java's built-in HttpClient, semantic request matching, and WireMock verification. It runs with the dependencies above.
import static com.github.tomakehurst.wiremock.client.WireMock.*;
import static org.junit.jupiter.api.Assertions.assertAll;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import org.junit.jupiter.api.Test;
import org.wiremock.junit5.WireMockRuntimeInfo;
import org.wiremock.junit5.WireMockTest;
@WireMockTest
class PaymentClientTest {
private final HttpClient client = HttpClient.newHttpClient();
@Test
void createsAnAuthorizedPayment(WireMockRuntimeInfo wireMock)
throws Exception {
stubFor(post(urlPathEqualTo("/v1/payments"))
.withHeader("Content-Type", containing("application/json"))
.withHeader("Idempotency-Key", equalTo("order-42"))
.withRequestBody(matchingJsonPath("$.amount", equalTo("1250")))
.withRequestBody(matchingJsonPath("$.currency", equalTo("USD")))
.willReturn(aResponse()
.withStatus(201)
.withHeader("Content-Type", "application/json")
.withBody("{\"paymentId\":\"pay-101\",\"status\":\"AUTHORIZED\"}")));
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(wireMock.getHttpBaseUrl() + "/v1/payments"))
.header("Content-Type", "application/json")
.header("Idempotency-Key", "order-42")
.POST(HttpRequest.BodyPublishers.ofString(
"{\"currency\":\"USD\",\"amount\":1250}"))
.build();
HttpResponse<String> response = client.send(
request, HttpResponse.BodyHandlers.ofString());
assertAll(
() -> assertEquals(201, response.statusCode()),
() -> assertTrue(response.body().contains("pay-101")),
() -> assertTrue(response.body().contains("AUTHORIZED"))
);
verify(1, postRequestedFor(urlPathEqualTo("/v1/payments"))
.withHeader("Idempotency-Key", equalTo("order-42")));
}
}
The body matchers ignore JSON property order while constraining the fields that matter. The response has a realistic status and content type. Verification checks the important idempotency behavior without copying every matcher. In an application test, call the real PaymentClient and assert its typed result rather than sending the HTTP request directly. This direct version keeps the WireMock API visible and runnable.
4. Request Matching Without Brittle Stubs
WireMock can match method, URL, path, query parameters, headers, cookies, authentication, body content, multipart fields, and other attributes. Prefer urlPathEqualTo plus separate query matchers when query order should not matter. Use urlEqualTo when the exact path and query string are truly the contract. Use regular expressions only when equality or path templates cannot express the rule clearly. Broad regexes frequently match unintended traffic.
For JSON, equalToJson compares structure rather than raw whitespace and can optionally ignore array order or extra elements. Use strict matching when the complete body belongs to the contract. Use matchingJsonPath for selected fields or predicates. WireMock matchers operate on strings after JSONPath selection, which is why a numeric selection can be compared with equalTo("1250") in the example. Current WireMock also supports JSON Schema request matching when full schema conformance is the right boundary.
Header matching should reflect protocol meaning. Content type often contains a charset, so containing("application/json") may be more robust than exact equality. Authorization values should not be embedded in committed examples. Inject safe test credentials and avoid writing secrets to mismatch diagnostics.
When no stub matches, WireMock returns a 404 response and its diagnostics can show near misses. Treat unexpected requests as failures in test-owned instances. Do not add a broad success catch-all to make tests green. A low-priority catch-all error can improve the response seen by the consumer, but the test should still expose that no intended mapping matched.
5. Responses, Priority, and Reusable JSON Mappings
A response definition can set status, headers, body, body file, delay, fault, proxy behavior, and transformers. Make the response realistic enough to exercise the consumer. If the real provider returns 201 with an identifier and a Location header, a 200 with {} is a poor substitute. Include required headers and representative optional fields, but keep generated values deterministic unless variability is under test.
Overlapping stubs require explicit thought. WireMock normally resolves matching mappings with registration behavior, and priority allows control. Priority 1 is highest. Give a narrow success mapping higher priority and a broader fallback lower priority. The fallback might return an authorization error for unmatched requests under /api/, making missing credentials obvious. Do not depend on file loading order.
{
"priority": 1,
"request": {
"method": "GET",
"urlPath": "/v1/customers/42"
},
"response": {
"status": 200,
"jsonBody": {
"id": "42",
"tier": "GOLD"
},
"headers": {
"Content-Type": "application/json"
}
}
}
Standalone mappings live under mappings, while larger response bodies can live under __files and be referenced with bodyFileName. Version these files with tests, name them by behavior, validate them as JSON in CI, and keep unrelated scenarios separate. Shared mappings should have owners and provider-version context. A central library that changes silently can break or weaken dozens of consumers.
6. Stateful WireMock Stubbing With Scenarios
Scenarios let mappings change behavior through named states. Every scenario begins in Scenario.STARTED. A matching mapping can return a response and move the scenario to another state; later mappings match that state. This is useful for polling, retries, expiring tokens, resource lifecycles, and stateful third-party workflows.
import static com.github.tomakehurst.wiremock.client.WireMock.*;
import static com.github.tomakehurst.wiremock.stubbing.Scenario.STARTED;
stubFor(get(urlEqualTo("/v1/jobs/job-9"))
.inScenario("job progress")
.whenScenarioStateIs(STARTED)
.willReturn(okJson("{\"status\":\"RUNNING\"}"))
.willSetStateTo("completed"));
stubFor(get(urlEqualTo("/v1/jobs/job-9"))
.inScenario("job progress")
.whenScenarioStateIs("completed")
.willReturn(okJson("{\"status\":\"COMPLETED\"}")));
This example returns running once and completed on later matches. A consumer test can assert polling interval, terminal state, and maximum attempts without waiting for a real provider. Name scenario states by domain meaning, not state1 and state2.
State adds coupling. Tests that share one server and scenario name can influence each other, especially in parallel. The Jupiter extension resets before each test method by default, but concurrency inside a method or external shared servers still needs unique scenario names or isolated instances. Prefer stateless mappings when a request attribute can select the behavior, such as a test-specific job ID.
Do not use scenarios to simulate an entire provider database. As state complexity grows, a lightweight fake service or provider sandbox may be clearer. WireMock scenarios are best for small, deterministic conversations whose transitions are directly relevant to the consumer.
7. Delays, Faults, Retries, and Resilience Tests
A consumer's failure behavior is often more valuable than another happy-path stub. WireMock can return error status codes, add fixed or distributed delays, and simulate lower-level faults. Map each simulation to a requirement. A fixed delay greater than the client's read timeout tests timeout classification. A 429 with a documented retry header tests backoff. A malformed JSON body tests parser and error mapping. An abrupt connection behavior tests network-failure handling.
Keep the failure deterministic. A random delay that sometimes exceeds a timeout makes CI results difficult to reproduce. If distribution behavior is the target, seed or bound the experiment and run it in a suitable resilience suite. For normal regression, choose values on each side of the boundary.
Verify retry counts and timing with tolerance, but avoid asserting exact scheduler milliseconds on a shared agent. Confirm that retryable outcomes are retried, terminal client errors are not, idempotency keys are stable, and the final application response is meaningful. POST retry is unsafe unless the provider and consumer use an idempotency mechanism.
Test recovery after failure. Return 503 for the first scenario state and success for the second, then verify the application succeeds within its attempt limit. Also test exhausted attempts and ensure threads, connections, and circuit-breaker state recover for later tests. Read API error handling and negative testing for a broader error taxonomy.
8. Dynamic Responses and Response Templating
Response templating can derive output from request data, path variables, query values, headers, or helper functions. It is useful for correlation IDs, resource identifiers, pagination links, and provider behavior where static fixtures would require many nearly identical mappings. Enable and scope templating according to the current WireMock documentation and selected deployment mode.
A template should make the stub clearer, not become a second implementation of the provider. If Handlebars logic contains complex pricing, authorization, and database-like state, the consumer test may pass against behavior the real provider never implements. Keep transformations small and deterministic. Assert the consumer's handling of the result, not the template itself.
Escape and encode derived content correctly. Untrusted request values inserted into JSON must remain valid JSON, and values inserted into headers or URLs need appropriate handling. Use the supported JSON helpers rather than manual string concatenation when generating structured bodies. Never reflect authorization headers or secrets.
Static mappings are easier to review and diagnose, so start there. Add templates when variation is part of the contract or significantly reduces duplication. For complex dynamic fake behavior, compare a dedicated fake service, containerized provider emulator, or contract-generated mock. The simplest tool that preserves the risk is the maintainable choice.
9. Verification, Request Journals, and Diagnostics
Stubbing controls what the dependency returns. Verification checks what the consumer sent. WireMock records received requests in its request journal and supports count and pattern verification. Verify behavior that matters: one charge request per idempotency key, an authorization header, no call after validation failure, or three bounded polling attempts.
Avoid verifying every incidental interaction. If the stub already requires content type and body fields, repeating every matcher in verify can duplicate maintenance without improving the oracle. Assert consumer output and durable state first, then add interaction verification where the provider call itself is a requirement. Negative verification is useful, but allow all asynchronous work to reach a defined terminal signal before asserting zero calls.
Mismatch diagnostics should be part of CI artifacts. Preserve the expected mapping, closest received request, method, path, safe headers, and redacted body. A generic consumer exception such as payment unavailable is insufficient when the actual problem is /v2/payment versus /v1/payments. Keep WireMock logs at a useful level for failed tests without flooding every passing build.
Reset mappings and requests at intentional boundaries. The Jupiter extension resets before each method. A long shared standalone server may require explicit reset or administrative isolation between suites. Never let one test's request journal satisfy another test's verification.
10. WireMock in Docker, CI, and Shared Environments
Embedded WireMock is ideal for Java component tests because the test owns process lifecycle and can use a dynamic port. A standalone JAR or Docker container is useful for non-Java consumers, local collaborative environments, and black-box integration jobs. The same JSON mappings can be mounted into the container, but lifecycle and isolation now belong to the job orchestration.
Use a pinned image tag, a unique network and port per job, a readiness check, and guaranteed cleanup. Do not share one mutable instance across parallel pipelines. If sharing is unavoidable, isolate mappings and requests by dedicated instances rather than trying to prefix every path. Protect the admin API from untrusted networks because it can change behavior and reveal request data.
CI should validate mapping JSON, start WireMock, wait for readiness, start the consumer with injected base URLs, execute tests, collect diagnostics on failure, and stop resources. Seed an unmatched request to prove the test fails as designed. Keep mappings and consumer changes in the same review when the dependency contract changes.
A mock should not become a permanent substitute for integration. Run provider contract verification or targeted sandbox tests at a suitable cadence. Monitor production errors for provider behavior absent from the mock, then update contracts and cases intentionally.
11. WireMock Stubbing Strategy by Test Layer
Different layers need different fidelity. The following reference keeps stubbing purposeful.
| Test layer | WireMock role | What remains real | Main oracle |
|---|---|---|---|
| Unit-like client test | Replace one HTTP provider | Serialization and client logic | Typed result and outbound request |
| Component test | Replace external dependencies | Service, database, internal rules | API result and durable state |
| Browser integration | Stabilize selected third parties | Browser and application stack | Customer-visible behavior |
| Contract test | Serve consumer expectations | Contract definitions | Provider compatibility |
| Resilience test | Inject specific failures | Consumer recovery stack | Retry, timeout, fallback, observability |
| End-to-end test | Usually limited or no stubbing | Deployed dependencies | Cross-system business outcome |
Do not use a stub at a layer where it removes the risk being evaluated. A checkout test intended to verify payment integration should not replace payment and still be called full end-to-end. Accurate naming helps stakeholders interpret release evidence.
Interview Questions and Answers
These WireMock stubbing questions focus on contract judgment and test architecture.
Q: What is the difference between a mock, stub, and fake?
A stub returns controlled responses for defined inputs. A mock often emphasizes verifying expected interactions. A fake is a working but simplified implementation with more behavior or state. WireMock supports stubbing and request verification, and limited state through scenarios, so the test's usage determines the role.
Q: Why use dynamic ports?
Dynamic ports prevent collisions across local processes and parallel CI jobs. The test reads the assigned base URL and injects it into the consumer. A fixed port is only justified by an inflexible system and then requires explicit isolation.
Q: How strict should request matching be?
Strict enough to reject contract-breaking consumer requests, but not coupled to irrelevant representation details. I match method, path, required query or headers, and semantic body fields. I avoid raw JSON string comparison and incidental headers unless they are truly required.
Q: When would you use a WireMock scenario?
I use scenarios for short deterministic conversations such as polling, first-failure-then-success, or token expiry. I avoid them when a request key can select behavior statelessly or when state grows into a provider implementation. Isolated state is essential for parallel tests.
Q: How do you test retries with WireMock?
I return a specific retryable failure, then success, and verify attempts, stable idempotency data, and final result. I also test exhausted attempts and a nonretryable response. Timing assertions use reasonable bounds rather than exact scheduler milliseconds.
Q: Does WireMock replace contract testing?
No. A local mapping can drift from the provider. Consumer-driven contract verification, schema checks, or provider sandbox tests are needed to show compatibility. WireMock then provides deterministic behavior for the consumer test.
Q: What causes an unmatched request?
Common causes are wrong path or query matching, header differences, raw body assumptions, an incorrect content type, or an overlapping mapping with unexpected priority. I inspect WireMock's closest-match diagnostics and the actual received request before loosening the matcher.
Q: How do you keep a large stub library maintainable?
I organize mappings by provider behavior, reuse body files carefully, pin provider contract versions, validate JSON, assign owners, and remove unused mappings. Shared defaults remain minimal, while test-specific behavior stays near the test. Unexpected requests fail visibly.
Common Mistakes
- Matching any request to a success response, which hides wrong paths, methods, and missing identity.
- Comparing raw JSON text and failing when harmless whitespace or property order changes.
- Requiring incidental headers that the provider contract does not define.
- Using a fixed port across parallel tests without allocation or isolation.
- Depending on mapping load order instead of explicit priority for overlaps.
- Building complex provider business logic inside response templates.
- Sharing scenario state across tests and creating order-dependent failures.
- Retrying unsafe operations without a stable idempotency key.
- Verifying every detail twice while failing to assert the consumer's business outcome.
- Letting recorded mappings become permanent without removing secrets, noise, and obsolete provider behavior.
Conclusion
WireMock stubbing is most effective when each mapping describes a deliberate consumer contract and each test asserts the consumer's outcome. Use semantic matching, realistic responses, dynamic lifecycle, explicit priorities, focused verification, and deterministic failure simulations.
Start with one external dependency and its highest-risk behavior. Build a stable success mapping, add contract-relevant negative cases, prove mismatch diagnostics in CI, and connect the stub library to a process that detects provider drift.
Interview Questions and Answers
What does WireMock do in an integration test?
WireMock replaces an HTTP dependency with deterministic request and response behavior. The consumer remains real, so the test can validate serialization, error mapping, retries, and business outcomes. It does not prove the live provider currently honors the same contract.
How do you choose WireMock request matchers?
I match the smallest set of attributes that defines a valid provider request, usually method, path, required query or headers, and semantic body fields. I avoid incidental headers and raw JSON text. The matcher should reject a contract bug without failing on harmless representation changes.
Why are dynamic ports a best practice?
They prevent collisions between developers, parallel test classes, and CI jobs. The extension supplies the actual base URL, which I inject into the consumer before startup. Fixed ports require extra allocation and cleanup controls.
How do WireMock scenarios work?
Mappings belong to a named scenario and can require a current state, beginning with `Scenario.STARTED`. A response can transition the scenario so later requests match different behavior. I use this for small conversations such as polling or retry recovery and isolate state per test.
How would you test retry logic using WireMock?
I configure a retryable outcome followed by success, then assert attempt count, stable idempotency information, backoff bounds, and final consumer state. Separate cases cover exhausted retries and a terminal response that must not retry. The simulation remains deterministic.
What is the purpose of WireMock verification?
Verification queries the request journal to confirm important outbound interactions, such as one charge with an idempotency key or no provider call after local validation fails. I do not duplicate every stub matcher. Consumer output and durable state remain primary assertions.
How can stubs drift from a provider?
Mappings may keep old paths, schemas, status codes, or assumptions after the provider changes. I link them to contract versions, validate schemas, use consumer-driven contracts where appropriate, and run selected provider sandbox checks. Recorded traffic is reviewed and sanitized rather than trusted forever.
When is WireMock the wrong tool?
It is the wrong boundary when the test must prove the real provider integration, or when a highly stateful fake becomes a second provider implementation. A contract test, provider emulator, test container, sandbox, or full integration may preserve the risk more honestly.
Frequently Asked Questions
What is WireMock stubbing?
It is the configuration of request patterns and controlled HTTP responses in WireMock. Tests use those mappings to isolate a consumer from a real dependency while checking success, error, latency, and interaction behavior.
Which WireMock version should I use in 2026?
WireMock 3.13.2 is the documented stable 3.x release used in this guide. WireMock 4 is available as a beta line, so evaluate and pin it separately if you accept possible breaking changes.
How do I use WireMock with JUnit 5?
Add the WireMock test dependency and annotate the class with `@WireMockTest`, or register a programmatic `WireMockExtension`. The declarative extension starts a dynamic-port server, resets before each test, configures the DSL, and exposes runtime information.
How do I match JSON in WireMock?
Use `equalToJson` for semantic whole-document comparison or `matchingJsonPath` for selected fields and predicates. Choose strictness from the provider contract rather than comparing raw strings or ignoring every extra field.
Can WireMock simulate timeouts and failures?
Yes. It can add response delay, return error or malformed responses, and simulate supported lower-level faults. Tie each case to a resilience requirement and keep ordinary regression behavior deterministic.
Does WireMock replace a real API integration test?
No. WireMock isolates consumer behavior but its mappings can drift from the provider. Keep provider contract verification, schema validation, or targeted real-environment tests according to risk.
Why does WireMock return 404?
The incoming request did not match a registered stub, unless a custom fallback changed the behavior. Inspect method, path, query, headers, body matchers, mapping priority, and WireMock's closest-match diagnostics before weakening the stub.
Related Guides
- API contract testing with Pact: A Practical Guide (2026)
- API error handling and negative testing: A Practical Guide (2026)
- API idempotency testing: A Practical Guide (2026)
- API pagination testing: A Practical Guide (2026)
- API rate limiting testing: A Practical Guide (2026)
- API security testing basics: A Practical Guide (2026)