WireMock: Mock HTTP Services for Spring REST Testing
Learn to use WireMock for mocking external HTTP services in Spring Boot tests.
20+ years shipping production Java in banking & fintech. Lessons pulled from things that broke in production.
- ✓Basic knowledge of Spring Boot testing (JUnit, TestRestTemplate or WebTestClient)
- ✓Familiarity with REST APIs and HTTP methods
- ✓Java 8+ and Maven/Gradle
- WireMock is a library for stubbing and mocking HTTP services in tests.
- It runs as a standalone server or embedded in JUnit tests.
- Use it to simulate external APIs, test error handling, and verify request/response contracts.
- Key features: request matching, response templating, record/playback, and fault injection.
Imagine you're testing a food delivery app that needs to check if a restaurant is open. Instead of calling the real restaurant API (which might be down or slow), you use WireMock to pretend you're the restaurant and always respond instantly. This lets you test your app's behavior without depending on external services.
Every Spring Boot service I've worked on eventually needs to talk to an external API. Whether it's a payment gateway, a third-party identity provider, or a weather service, your code's correctness depends on how it handles those HTTP calls. And that's exactly where most integration tests fall short.
I remember a fintech startup I consulted for: their CI pipeline was a nightmare. Tests would fail randomly because the test Stripe account would occasionally return 429 rate limits or 500 errors. The team spent hours blaming each other's code, only to realize the external API was the culprit. That's when I introduced WireMock.
WireMock lets you stub HTTP endpoints, simulate delays, inject faults, and even verify that your code sent the right request. It's not just about making tests pass — it's about making them deterministic. In this article, I'll show you how to integrate WireMock with Spring Boot tests, what the official docs gloss over, and how to debug it when things go sideways.
Setting Up WireMock in Spring Boot
Let's start with the basics. The fastest way to get WireMock into your Spring Boot test is via the spring-cloud-contract-wiremock starter, but I prefer the standalone WireMock library for more control. Here's my go-to setup:
Add the dependency to your pom.xml:
``xml <dependency> <groupId>com.github.tomakehurst</groupId> <artifactId>wiremock-jre8</artifactId> <version>2.35.0</version> <scope>test</scope> </dependency> ``
Then in your test, use @WireMockTest to automatically start a WireMock server on a random port. Combine it with Spring's @SpringBootTest and @TestRestTemplate for a full integration test.
Here's a typical example: we have a service that calls an external API to get exchange rates. We'll stub that API with WireMock.
Request Matching and Response Templating
WireMock's request matching is powerful. You can match on URL, method, headers, query parameters, and body. For dynamic responses, use response templating with Handlebars.
Consider a payment service that expects a JSON payload with a transaction_id. You can match the body and return a dynamic response:
``java stubFor(post(urlEqualTo("/payments")) .withRequestBody(matchingJsonPath("$.amount")) .willReturn(aResponse() .withStatus(201) .withHeader("Content-Type", "application/json") .withBody("{ \"id\": \"{{randomValue length=20 type='ALPHANUMERIC'}}\", \"status\": \"approved\" }") .withTransformers("response-template"))); ``
This uses the response-template transformer to generate a random ID. You need to enable the transformer globally or per stub.
For more complex scenarios, use WireMock's scenario feature to simulate stateful behavior, like a payment that is first pending then confirmed.
Verifying Requests and Asserting Interactions
Testing that your service sent the correct request is just as important as stubbing the response. WireMock allows you to verify that a specific request was made, with exact matching.
For example, after calling your payment service, verify that it sent the correct payload to the external API:
``java verify(postRequestedFor(urlEqualTo("/payments")) .withRequestBody(matchingJsonPath("$.amount", equalTo("100.00"))) .withHeader("Authorization", containing("Bearer"))); ``
You can also verify the number of calls:
``java verify(exactly(1), postRequestedFor(urlEqualTo("/payments"))); ``
This is crucial for ensuring your service doesn't make duplicate requests or miss calls entirely.
verify() to assert that your service made the expected HTTP calls with correct parameters.Simulating Faults and Edge Cases
One of WireMock's superpowers is fault injection. You can simulate timeouts, connection resets, malformed responses, and HTTP errors. This is invaluable for testing your error handling and retry logic.
For example, to simulate a slow API:
``java stubFor(get(urlEqualTo("/slow")) .willReturn(aResponse() .withFixedDelay(5000) .withStatus(200))); ``
Or a connection reset:
``java stubFor(get(urlEqualTo("/reset")) .willReturn(aResponse().withFault(Fault.CONNECTION_RESET_BY_PEER))); ``
Combine this with Spring's @Retryable to ensure your service handles transient failures gracefully.
What the Official Docs Won't Tell You
After years of using WireMock in production-grade Spring Boot applications, here are the gotchas that the official documentation glosses over:
1. Static state pollution across test classes WireMock server, when used as a static field or via @WireMockTest, holds state across tests. If you don't reset it, stubs from one test can affect another. The official docs mention resetAll() but don't emphasize that you must call it in @BeforeEach or @AfterEach. I've seen CI builds fail randomly because of this.
2. Response templating is not enabled by default The response-template transformer must be registered explicitly. Many developers assume it works out of the box and waste hours debugging why {{randomValue}} isn't generating random data.
3. JSON matching with equalToJson can be tricky By default, equalToJson is strict about field ordering and whitespace. Use the lenient mode: equalToJson(body, true, true). The second parameter is ignoreArrayOrder, the third is ignoreExtraElements. Without these, tests can fail due to JSON formatting differences.
4. WireMock's HTTPS support requires extra setup If your application calls HTTPS endpoints, WireMock can simulate that, but you need to configure a keystore. The docs cover it, but most examples use HTTP. In production, your external APIs are likely HTTPS, so your tests should reflect that.
5. Record-playback can be a maintenance nightmare WireMock can record real API responses and play them back. It sounds great, but the recorded stubs are fragile. If the API changes even slightly, your tests break. I recommend using it only for initial test creation, then hand-tuning stubs for maintainability.
Integrating WireMock with Spring Cloud Contract
If you're working in a microservices ecosystem, Spring Cloud Contract (SCC) builds on top of WireMock to provide contract testing. SCC generates WireMock stubs from contract definitions, which can be shared between producer and consumer services.
Here's a minimal setup: add the spring-cloud-starter-contract-verifier dependency and write a contract in Groovy or YAML. SCC will generate tests and WireMock stubs automatically.
But be warned: SCC adds complexity. For simple projects, plain WireMock is often sufficient. I've seen teams over-engineer their testing with SCC when all they needed was a few stubs.
The Case of the Flaky CI Build
- Always reset WireMock state between tests, especially when using static or shared instances.
- Use @DirtiesContext judiciously — it slows down tests but prevents cross-test pollution.
- Consider using WireMock's scenario feature for stateful testing, but document it clearly.
- Never assume tests are isolated; verify by running the full suite.
- Add a WireMock reset listener in JUnit's after-all callback for extra safety.
wireMockServer.setLogging(true);Check logs for request details| File | Command / Code | Purpose |
|---|---|---|
| ExchangeRateServiceTest.java | @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) | Setting Up WireMock in Spring Boot |
| PaymentServiceTest.java | @Test | Request Matching and Response Templating |
| VerificationExampleTest.java | @Test | Verifying Requests and Asserting Interactions |
| FaultInjectionTest.java | @Test | Simulating Faults and Edge Cases |
| WireMockHttpsTest.java | @WireMockTest(httpsEnabled = true) | What the Official Docs Won't Tell You |
| ContractTest.groovy | Contract.make { | Integrating WireMock with Spring Cloud Contract |
Key takeaways
Interview Questions on This Topic
How do you stub a POST endpoint with a JSON body in WireMock?
stubFor(post(urlEqualTo("/endpoint")).withRequestBody(equalToJson("{...}")).willReturn(aResponse().withStatus(201))).Frequently Asked Questions
20+ years shipping production Java in banking & fintech. Lessons pulled from things that broke in production.
That's Spring Boot. Mark it forged?
3 min read · try the examples if you haven't