StableMock is a JUnit 5 extension that automatically records third-party API calls and replays them using WireMock — without manual setup, Docker, or brittle matchers.
Perfect for mocking external/third-party APIs • Fast, lightweight HTTP mocking without Docker overhead • Tests with dynamic request data that need intelligent pattern matching
Auto-mocking for your tests! StableMock automatically records and replays HTTP calls to external APIs.
Zero-config recording • Smart pattern matching • Free forever
This test runs twice automatically:
StableMock compares the two runs to detect which fields change between executions, then automatically creates ignore patterns so your tests remain stable even when request data varies.
@U(urls = { "https://api1.com", "https://api2.com" },
properties = { "app.api1.url", "app.api2.url" })
@SpringBootTest
class MyTest extends BaseStableMockTest {
@DynamicPropertySource
static void configureProperties(DynamicPropertyRegistry registry) {
autoRegisterProperties(registry, MyTest.class);
}
@Test
public void myTest() {
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("http://localhost:8080/api/users"))
.GET()
.build();
HttpResponse<String> response = client.send(
request, HttpResponse.BodyHandlers.ofString()
);
assertEquals(200, response.statusCode());
}
} That's it. No configuration. No setup. Just works. Even with everchanging request attributes.
Use multiple @U annotations in the same test method with different URLs, scenarios, and ignore patterns.
@U(urls = "https://api1.example.com", scenario = true, ignore = { "Date", "Connection" })
@U(urls = "https://api2.example.com", ignore = { "json:timestamp", "json:requestId" })
@SpringBootTest
class AdvancedTest extends BaseStableMockTest {
@DynamicPropertySource
static void configureProperties(DynamicPropertyRegistry registry) {
autoRegisterProperties(registry, AdvancedTest.class);
}
@Test
public void testMultipleServices() {
HttpClient client = HttpClient.newHttpClient();
String baseUrl = "http://localhost:8080";
// Scenario mode: Sequential responses from api1
// First call returns "pending"
HttpRequest req1 = HttpRequest.newBuilder()
.uri(URI.create(baseUrl + "/api1/job/status"))
.GET()
.build();
HttpResponse<String> resp1 = client.send(req1, HttpResponse.BodyHandlers.ofString());
// Second call returns "processing" (different response)
HttpResponse<String> resp2 = client.send(req1, HttpResponse.BodyHandlers.ofString());
// Explicit ignore patterns: api2 ignores dynamic fields
String body = String.format(
"{"action":"create","timestamp":%d,"requestId":"%s"}",
System.currentTimeMillis(),
UUID.randomUUID().toString()
);
HttpRequest req2 = HttpRequest.newBuilder()
.uri(URI.create(baseUrl + "/api2/users"))
.POST(HttpRequest.BodyPublishers.ofString(body))
.header("Content-Type", "application/json")
.build();
HttpResponse<String> resp3 = client.send(req2, HttpResponse.BodyHandlers.ofString());
assertEquals(200, resp1.statusCode());
assertEquals(200, resp2.statusCode());
assertEquals(200, resp3.statusCode());
}
}
Multiple @U annotations merge URLs and ignore patterns. Scenario mode enables sequential responses for the same endpoint.
Learn how to master integration testing in Spring Boot.
The easiest way to mock external APIs like Stripe or Salesforce in Spring Boot without manual config.
Read Guide →An honest comparison. Why we built StableMock on top of WireMock and when you should use which.
Compare Tools →The complete 2026 guide to integration testing microservices with Spring Boot and Testcontainers.
Start Learning →Record once. Test offline forever.
Join developers who've made the switch. Free forever. No credit card required.
🌾 MIT License | 🚀 Open Source | 💯 Free Forever