.claude/skills/wiremock/SKILL.md
Expert guidance for using WireMock in Java applications for HTTP API mocking and testing. Use this skill when the user asks to mock HTTP APIs, create API stubs, test REST clients, simulate network faults, verify HTTP requests, or integrate WireMock with Spring Boot. Trigger keywords include "wiremock", "mock http", "stub api", "http mock", "api testing", "rest mock", "simulate fault", "verify request", "spring boot wiremock".
npx skillsauth add aehrc/pathling wiremockInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
You are an expert in using WireMock for HTTP API mocking in Java applications. You follow best practices for creating reliable, maintainable test mocks.
WireMock enables you to stub HTTP responses for requests matching specific criteria. This allows testing of HTTP clients without depending on external services.
The preferred approaches for configuring stubs are:
mappings directory (for test resources)These methods work well with ephemeral WireMock instances in test contexts, avoiding the need for persistent WireMock servers.
Every stub requires:
import static com.github.tomakehurst.wiremock.client.WireMock.*;
stubFor(get(urlEqualTo("/api/users"))
.willReturn(aResponse()
.withStatus(200)
.withHeader("Content-Type", "application/json")
.withBody("{\"users\": []}")));
WireMock provides convenient methods for common patterns:
stubFor(get("/ping").willReturn(ok()));
stubFor(get("/users").willReturn(okJson("{\"users\": []}")));
stubFor(post("/redirect").willReturn(temporaryRedirect("/new/location")));
stubFor(get("/error").willReturn(serverError()));
stubFor(get("/not-found").willReturn(notFound()));
stubFor(get("/unauthorized").willReturn(unauthorized()));
String literal:
.withBody("Hello, world!")
JSON object:
.withBody("{\"message\": \"Hello\"}")
// Or use the convenience method:
.willReturn(okJson("{\"message\": \"Hello\"}"))
File reference:
.withBodyFile("response.json") // Relative to __files directory
Binary data:
.withBody(binaryData) // Base64-encoded in JSON
When multiple stubs match a request, priority determines which responds. Lower numbers equal higher priority (1 is highest; default is 5).
stubFor(get(urlEqualTo("/api/users/123"))
.atPriority(1)
.willReturn(aResponse().withStatus(200).withBody("Specific user")));
stubFor(get(urlMatching("/api/users/.*"))
.atPriority(5)
.willReturn(aResponse().withStatus(200).withBody("Any user")));
JSON mapping files provide a declarative way to define stubs that can be version-controlled and reused across tests.
Place mapping files in the test resources directory:
src/test/resources/
├── mappings/
│ ├── get-users.json
│ ├── create-user.json
│ └── error-scenarios.json
└── __files/
├── users-response.json
└── user-created-response.json
{
"request": {
"method": "GET",
"urlPath": "/api/users"
},
"response": {
"status": 200,
"headers": {
"Content-Type": "application/json"
},
"body": "{\"users\": []}"
}
}
For larger responses, reference files from the __files directory:
{
"request": {
"method": "GET",
"urlPath": "/api/users"
},
"response": {
"status": 200,
"headers": {
"Content-Type": "application/json"
},
"bodyFileName": "users-response.json"
}
}
URL path with query parameters:
{
"request": {
"method": "GET",
"urlPath": "/api/search",
"queryParameters": {
"q": {
"equalTo": "test"
},
"page": {
"matches": "[0-9]+"
}
}
},
"response": {
"status": 200
}
}
Header matching:
{
"request": {
"method": "POST",
"urlPath": "/api/users",
"headers": {
"Content-Type": {
"equalTo": "application/json"
},
"Authorization": {
"matches": "Bearer .*"
}
}
},
"response": {
"status": 201
}
}
Request body matching:
{
"request": {
"method": "POST",
"urlPath": "/api/users",
"bodyPatterns": [
{
"matchesJsonPath": "$.name"
},
{
"matchesJsonPath": "$.email",
"equalTo": "[email protected]"
}
]
},
"response": {
"status": 201
}
}
{
"priority": 1,
"request": {
"method": "GET",
"urlPath": "/api/users/123"
},
"response": {
"status": 200,
"body": "Specific user"
}
}
Spring Boot integration:
@EnableWireMock({
@ConfigureWireMock(
name = "api-service",
filesUnderClasspath = "wiremock"
)
})
This loads mappings from src/test/resources/wiremock/mappings/.
Programmatic loading:
WireMockServer wireMockServer = new WireMockServer(
options()
.usingFilesUnderClasspath("wiremock")
.port(8080)
);
wireMockServer.start();
JSON mappings provide baseline stubs, while programmatic stubs handle test-specific scenarios:
@Test
void testSpecificScenario() {
// JSON mappings already loaded from src/test/resources/mappings/
// Override or add test-specific stub
stubFor(get("/api/users/999")
.willReturn(notFound()));
// Run test
assertThrows(UserNotFoundException.class, () -> {
apiClient.getUser(999);
});
}
Exact URL match (including query parameters):
stubFor(get(urlEqualTo("/api/users?page=1"))
.willReturn(ok()));
URL regex:
stubFor(get(urlMatching("/api/users/[0-9]+"))
.willReturn(ok()));
Path-only equality (preferred for query parameters):
stubFor(get(urlPathEqualTo("/api/users"))
.willReturn(ok()));
Path-only regex:
stubFor(get(urlPathMatching("/api/users/.*"))
.willReturn(ok()));
Path templates (RFC 6570 compliant):
stubFor(get(urlPathTemplate("/contacts/{contactId}/addresses/{addressId}"))
.willReturn(ok()));
stubFor(get(urlPathEqualTo("/api/users"))
.withQueryParam("page", equalTo("1"))
.withQueryParam("size", matching("[0-9]+"))
.willReturn(ok()));
stubFor(get(urlEqualTo("/api/users"))
.withHeader("Accept", equalTo("application/json"))
.withHeader("Authorization", matching("Bearer .*"))
.willReturn(ok()));
stubFor(get(urlEqualTo("/api/users"))
.withHeader("X-Custom-Header", absent())
.willReturn(ok()));
Exact match:
stubFor(post(urlEqualTo("/api/users"))
.withRequestBody(equalTo("{\"name\": \"John\"}"))
.willReturn(created()));
Contains:
stubFor(post(urlEqualTo("/api/users"))
.withRequestBody(containing("John"))
.willReturn(created()));
Regex:
stubFor(post(urlEqualTo("/api/users"))
.withRequestBody(matching(".*\"name\"\\s*:\\s*\"[A-Z][a-z]+\".*"))
.willReturn(created()));
JSON equality:
stubFor(post(urlEqualTo("/api/users"))
.withRequestBody(equalToJson("{\"name\": \"John\", \"age\": 30}", true, true))
.willReturn(created()));
JSON path matching:
stubFor(post(urlEqualTo("/api/users"))
.withRequestBody(matchingJsonPath("$.name"))
.withRequestBody(matchingJsonPath("$.age", equalTo("30")))
.willReturn(created()));
JSON schema validation:
stubFor(post(urlEqualTo("/api/users"))
.withRequestBody(matchingJsonSchema(schemaJson))
.willReturn(created()));
XML equality:
stubFor(post(urlEqualTo("/api/users"))
.withRequestBody(equalToXml("<user><name>John</name></user>"))
.willReturn(created()));
XPath matching:
stubFor(post(urlEqualTo("/api/users"))
.withRequestBody(matchingXPath("//name[text()='John']"))
.willReturn(created()));
stubFor(get(urlEqualTo("/api/protected"))
.withBasicAuth("username", "password")
.willReturn(ok()));
stubFor(get(urlEqualTo("/api/users"))
.withCookie("session", matching("[A-Z0-9]+"))
.willReturn(ok()));
AND combination:
stubFor(post(urlEqualTo("/api/users"))
.withRequestBody(matching("[a-z]+").and(containing("magic")))
.willReturn(ok()));
OR combination:
stubFor(get(urlEqualTo("/api/users"))
.withHeader("Accept", matching("application/json").or(matching("application/xml")))
.willReturn(ok()));
NOT negation:
stubFor(get(urlEqualTo("/api/users"))
.withHeader("X-Internal", not(matching("true")))
.willReturn(ok()));
Add delays to individual stubs:
stubFor(get(urlEqualTo("/api/slow"))
.willReturn(aResponse()
.withStatus(200)
.withFixedDelay(2000))); // 2 second delay
Lognormal distribution (realistic long-tail latencies):
stubFor(get(urlEqualTo("/api/variable"))
.willReturn(aResponse()
.withStatus(200)
.withLogNormalRandomDelay(90, 0.1))); // Median 90ms, sigma 0.1
Uniform distribution (stable latency with jitter):
stubFor(get(urlEqualTo("/api/jitter"))
.willReturn(aResponse()
.withStatus(200)
.withUniformRandomDelay(15, 25))); // Between 15-25ms
Simulate slow network connections by fragmenting responses:
stubFor(get(urlEqualTo("/api/slow-download"))
.willReturn(aResponse()
.withBody("Large response body...")
.withChunkedDribbleDelay(5, 1000))); // 5 chunks over 1000ms
Simulate corrupted or failed responses:
import static com.github.tomakehurst.wiremock.http.Fault.*;
stubFor(get(urlEqualTo("/api/fault"))
.willReturn(aResponse().withFault(MALFORMED_RESPONSE_CHUNK)));
Available fault types:
EMPTY_RESPONSE: No response dataMALFORMED_RESPONSE_CHUNK: Valid status, then corrupted dataRANDOM_DATA_THEN_CLOSE: Garbage followed by connection closureCONNECTION_RESET_BY_PEER: Immediate disconnectionVerify a request was made:
import static com.github.tomakehurst.wiremock.client.WireMock.*;
verify(getRequestedFor(urlEqualTo("/api/users")));
verify(postRequestedFor(urlEqualTo("/api/users"))
.withHeader("Content-Type", equalTo("application/json")));
Verify exact request counts:
verify(exactly(3), postRequestedFor(urlEqualTo("/api/users")));
verify(lessThan(5), getRequestedFor(urlMatching("/api/.*")));
verify(moreThanOrExactly(1), deleteRequestedFor(urlEqualTo("/api/users/123")));
Available count operators:
exactly(n)lessThan(n)lessThanOrExactly(n)moreThan(n)moreThanOrExactly(n)List<ServeEvent> allEvents = getAllServeEvents();
List<LoggedRequest> allRequests = findAll(anyRequestedFor(anyUrl()));
List<LoggedRequest> userRequests = findAll(
putRequestedFor(urlMatching("/api/users/.*")));
List<LoggedRequest> unmatchedRequests = findUnmatchedRequests();
Identify stub mappings that almost matched (useful for debugging):
List<NearMiss> nearMisses = findNearMissesForAllUnmatchedRequests();
resetAllRequests(); // Clear request log only
reset(); // Clear both stubs and request log
Add the dependency:
Maven:
<dependency>
<groupId>org.wiremock.integrations</groupId>
<artifactId>wiremock-spring-boot</artifactId>
<version>3.10.0</version>
<scope>test</scope>
</dependency>
Gradle:
testImplementation 'org.wiremock.integrations:wiremock-spring-boot:3.10.0'
import org.springframework.boot.test.context.SpringBootTest;
import org.wiremock.spring.EnableWireMock;
import org.wiremock.spring.ConfigureWireMock;
import org.springframework.beans.factory.annotation.Value;
import static com.github.tomakehurst.wiremock.client.WireMock.*;
@SpringBootTest
@EnableWireMock
class ApiClientTest {
@Value("${wiremock.server.baseUrl}")
private String wireMockUrl;
@Test
void testApiClient() {
stubFor(get("/api/users")
.willReturn(okJson("[{\"id\": 1, \"name\": \"John\"}]")));
// Test your API client
List<User> users = apiClient.getUsers();
assertThat(users).hasSize(1);
verify(getRequestedFor(urlEqualTo("/api/users")));
}
}
The integration automatically provides:
wiremock.server.baseUrl — Server base URLwiremock.server.port — HTTP port number@SpringBootTest
@EnableWireMock({
@ConfigureWireMock(
name = "user-service",
port = 8888,
baseUrlProperties = "user-service.url"
)
})
class ApiClientTest {
@Value("${user-service.url}")
private String userServiceUrl;
}
Configuration options:
port — HTTP port (default: random)httpsPort — HTTPS port (default: random)name — Instance identifier (default: "wiremock")baseUrlProperties — Custom property name for base URLportProperties — Custom property name for portfilesUnderClasspath — Classpath resource root for mappingsfilesUnderDirectory — Directory root for mappingsimport org.wiremock.spring.InjectWireMock;
import com.github.tomakehurst.wiremock.WireMockServer;
@SpringBootTest
@EnableWireMock
class ApiClientTest {
@InjectWireMock
private WireMockServer wireMock;
@Test
void testWithDirectAccess() {
wireMock.stubFor(get("/api/users").willReturn(ok()));
// Test code
}
}
@SpringBootTest
@EnableWireMock({
@ConfigureWireMock(
name = "user-service",
baseUrlProperties = "user-service.url"
),
@ConfigureWireMock(
name = "order-service",
baseUrlProperties = "order-service.url"
)
})
class MultiServiceTest {
@InjectWireMock("user-service")
private WireMockServer userService;
@InjectWireMock("order-service")
private WireMockServer orderService;
@Test
void testMultipleServices() {
userService.stubFor(get("/users/1").willReturn(okJson("{\"id\": 1}")));
orderService.stubFor(get("/orders").willReturn(okJson("[]")));
// Test code that calls both services
}
}
For advanced control, implement WireMockConfigurationCustomizer:
import org.wiremock.spring.WireMockConfigurationCustomizer;
import com.github.tomakehurst.wiremock.core.WireMockConfiguration;
@SpringBootTest
@EnableWireMock({
@ConfigureWireMock(
configurationCustomizers = CustomizerTest.Customizer.class
)
})
class CustomizerTest {
static class Customizer implements WireMockConfigurationCustomizer {
@Override
public void customize(
WireMockConfiguration configuration,
ConfigureWireMock options
) {
configuration
.notifier(new CustomNotifier())
.extensions(new CustomTransformer());
}
}
}
List<StubMapping> allStubs = WireMock.listAllStubMappings().getMappings();
StubMapping stub = stubFor(get("/api/users").willReturn(ok()));
stub.setResponse(aResponse().withStatus(404));
editStub(stub);
// Remove by ID
removeStub(stubId);
// Remove by metadata
removeStubsByMetadata(matchingJsonPath("$.tag", equalTo("temporary")));
// Remove all
WireMock.resetMappings();
List<StubMapping> unusedStubs = findUnmatchedStubs();
Prefer urlPathEqualTo() over urlEqualTo() when matching query parameters separately to achieve order-invariant matching:
// Good: Order-independent query parameter matching
stubFor(get(urlPathEqualTo("/api/search"))
.withQueryParam("q", equalTo("test"))
.withQueryParam("page", equalTo("1"))
.willReturn(ok()));
// Avoid: Query parameter order matters
stubFor(get(urlEqualTo("/api/search?q=test&page=1"))
.willReturn(ok()));
Choose the most specific matcher for your use case:
equalTo() for exact matchesmatching() for regex patternscontaining() for substring checksmatchingJsonPath() for JSON field matchingmatchingJsonSchema() for structural validationUse priority to create layered stub configurations:
Always reset WireMock state between tests:
@BeforeEach
void setUp() {
WireMock.reset(); // Clears both stubs and request log
}
Or reset selectively:
@BeforeEach
void setUp() {
WireMock.resetAllRequests(); // Keep stubs, clear request log
}
When simulating latency, use lognormal distribution for realistic long-tail behaviour:
stubFor(get("/api/users")
.willReturn(aResponse()
.withStatus(200)
.withLogNormalRandomDelay(100, 0.1)));
Always verify that critical requests were made:
@Test
void shouldCreateUser() {
// Stub the response
stubFor(post("/api/users").willReturn(created()));
// Execute test
apiClient.createUser(new User("John"));
// Verify the request
verify(postRequestedFor(urlEqualTo("/api/users"))
.withRequestBody(matchingJsonPath("$.name", equalTo("John"))));
}
When using multiple WireMock instances in Spring Boot, use descriptive names:
@EnableWireMock({
@ConfigureWireMock(name = "user-service"),
@ConfigureWireMock(name = "payment-gateway"),
@ConfigureWireMock(name = "notification-service")
})
Use JSON mapping files for stubs that are reused across multiple tests:
src/test/resources/mappings/
├── common-success-responses.json
├── common-error-responses.json
└── service-specific/
├── user-service-stubs.json
└── order-service-stubs.json
Reference external files for large response bodies:
{
"request": {
"method": "GET",
"urlPath": "/api/users"
},
"response": {
"status": 200,
"headers": {
"Content-Type": "application/json"
},
"bodyFileName": "users-response.json"
}
}
Reserve programmatic stub creation for test-specific cases that require dynamic behaviour:
@Test
void shouldHandleSpecificEdgeCase() {
// JSON mappings provide baseline stubs
// Add test-specific stub programmatically
stubFor(get("/api/users/edge-case")
.willReturn(aResponse()
.withStatus(200)
.withBody(generateDynamicResponse())));
// Test code
}
Always test how your code handles failures:
@Test
void shouldHandleServerError() {
stubFor(get("/api/users").willReturn(serverError()));
assertThrows(ServiceException.class, () -> {
apiClient.getUsers();
});
}
@Test
void shouldHandleTimeout() {
stubFor(get("/api/users")
.willReturn(aResponse()
.withStatus(200)
.withFixedDelay(5000))); // Longer than client timeout
assertThrows(TimeoutException.class, () -> {
apiClient.getUsers();
});
}
Simulate stateful interactions using scenarios:
stubFor(post("/api/orders")
.inScenario("Order Flow")
.whenScenarioStateIs(STARTED)
.willReturn(created())
.willSetStateTo("Order Created"));
stubFor(get("/api/orders/123")
.inScenario("Order Flow")
.whenScenarioStateIs("Order Created")
.willReturn(okJson("{\"id\": 123, \"status\": \"PENDING\"}")));
Use response templates for dynamic responses (requires extension):
stubFor(get(urlPathMatching("/api/users/([0-9]+)"))
.willReturn(aResponse()
.withBody("{\"id\": {{request.pathSegments.[2]}}, \"name\": \"User\"}")
.withTransformers("response-template")));
stubFor(post("/api/users")
.withRequestBody(matchingJsonPath("$.role", equalTo("admin")))
.willReturn(aResponse()
.withStatus(201)
.withBody("{\"role\": \"admin\"}")));
stubFor(post("/api/users")
.withRequestBody(matchingJsonPath("$.role", equalTo("user")))
.willReturn(aResponse()
.withStatus(201)
.withBody("{\"role\": \"user\"}")));
stubFor(get("/api/data")
.inScenario("Rate Limit")
.whenScenarioStateIs(STARTED)
.willReturn(ok())
.willSetStateTo("Request 1"));
stubFor(get("/api/data")
.inScenario("Rate Limit")
.whenScenarioStateIs("Request 1")
.willReturn(ok())
.willSetStateTo("Request 2"));
stubFor(get("/api/data")
.inScenario("Rate Limit")
.whenScenarioStateIs("Request 2")
.willReturn(aResponse()
.withStatus(429)
.withHeader("Retry-After", "60")));
documentation
Expert guidance for implementing SQL on FHIR v2 ViewDefinitions and operations to create portable, tabular projections of FHIR data. Use this skill when the user asks to create ViewDefinitions, flatten FHIR resources into tables, write FHIRPath expressions for data extraction, implement forEach/forEachOrNull/repeat patterns for unnesting, create where clauses for filtering, use constants in view definitions, combine data with unionAll, execute ViewDefinitions with $run or $export operations, or implement SQL on FHIR server capabilities. Trigger keywords include "ViewDefinition", "SQL on FHIR", "flatten FHIR", "tabular FHIR", "FHIR to SQL", "FHIR analytics", "FHIRPath columns", "unnest FHIR", "$viewdefinition-run", "$export", "view runner", "repeat", "recursive", "QuestionnaireResponse".
development
Expert guidance for working with the Apache Spark Catalyst query optimisation framework. Use this skill when working with Spark SQL internals, creating custom expressions, implementing query optimisations, working with logical/physical plans, or extending Catalyst. Trigger keywords include "catalyst", "spark sql", "expression", "logical plan", "physical plan", "tree node", "query optimisation", "rule executor", "analyzer", "optimizer", "code generation".
development
Expert guidance for using the SonarCloud API to interact with code quality analysis, projects, issues, quality gates, and metrics. Use this skill when making API calls to SonarCloud, automating code quality workflows, retrieving analysis results, managing projects programmatically, or integrating SonarCloud with CI/CD pipelines. Trigger keywords include "SonarCloud", "SonarCloud API", "code quality API", "SonarQube Cloud", "quality gate", "code analysis API", "SonarCloud measures", "SonarCloud issues".
tools
Expert guidance for implementing SMART App Launch (HL7 FHIR specification for OAuth 2.0-based authorization). Use this skill when implementing FHIR app authorization, EHR launch sequences, standalone app launch, backend services authentication, SMART scopes, token handling, or capability discovery. Trigger keywords include "SMART", "SMART on FHIR", "EHR launch", "standalone launch", "FHIR authorization", "FHIR OAuth", "backend services", "system scopes", "patient scopes", "fhirUser", ".well-known/smart-configuration", "PKCE", "client_credentials", "launch context".