WireMock is a powerful library for stubbing and mocking web services, enabling comprehensive testing of HTTP-based APIs in Java applications. It allows you to create realistic API simulations for testing, development, and demo environments.
What is WireMock?
WireMock is a flexible library for building mock HTTP servers that can simulate API responses, record and playback interactions, and verify API calls. It's particularly useful for:
- Testing - Isolating components by mocking external dependencies
- Development - Working against realistic API simulations when real services are unavailable
- Performance Testing - Simulating various API response times and behaviors
- Contract Testing - Ensuring API consumers and providers adhere to agreed contracts
Setting Up WireMock
1. Maven Dependencies
<dependencies> <!-- WireMock Core --> <dependency> <groupId>com.github.tomakehurst</groupId> <artifactId>wiremock-jre8</artifactId> <version>2.35.0</version> <scope>test</scope> </dependency> <!-- JUnit 5 Support --> <dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter</artifactId> <version>5.9.2</version> <scope>test</scope> </dependency> <!-- REST Assured for API Testing --> <dependency> <groupId>io.rest-assured</groupId> <artifactId>rest-assured</artifactId> <version>5.3.0</version> <scope>test</scope> </dependency> <!-- JSON Assertion Library --> <dependency> <groupId>org.skyscreamer</groupId> <artifactId>jsonassert</artifactId> <version>1.5.1</version> <scope>test</scope> </dependency> </dependencies>
2. Basic WireMock Setup
// BaseWireMockTest.java import com.github.tomakehurst.wiremock.junit5.WireMockExtension; import org.junit.jupiter.api.extension.RegisterExtension; import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig; public abstract class BaseWireMockTest { @RegisterExtension static WireMockExtension wireMockServer = WireMockExtension.newInstance() .options(wireMockConfig().port(8089)) .build(); } Core WireMock Features
1. Basic Stubbing
// BasicStubbingTest.java import org.junit.jupiter.api.Test; import static com.github.tomakehurst.wiremock.client.WireMock.*; import static io.restassured.RestAssured.given; import static org.hamcrest.Matchers.equalTo; class BasicStubbingTest extends BaseWireMockTest { @Test void testBasicGetStub() { // Stub a GET request stubFor(get(urlEqualTo("/api/users/1")) .willReturn(aResponse() .withStatus(200) .withHeader("Content-Type", "application/json") .withBody(""" { "id": 1, "name": "John Doe", "email": "[email protected]" } """))); // Verify the stub works given() .port(8089) .when() .get("/api/users/1") .then() .statusCode(200) .body("name", equalTo("John Doe")) .body("email", equalTo("[email protected]")); } @Test void testPostWithRequestBodyMatching() { // Stub a POST request with request body matching stubFor(post(urlEqualTo("/api/users")) .withRequestBody(equalToJson(""" { "name": "Jane Smith", "email": "[email protected]" } """)) .willReturn(aResponse() .withStatus(201) .withHeader("Content-Type", "application/json") .withBody(""" { "id": 2, "name": "Jane Smith", "email": "[email protected]", "createdAt": "2023-01-01T00:00:00Z" } """))); // Test the POST request given() .port(8089) .contentType("application/json") .body(""" { "name": "Jane Smith", "email": "[email protected]" } """) .when() .post("/api/users") .then() .statusCode(201) .body("id", equalTo(2)); } @Test void testQueryParametersStubbing() { // Stub with query parameters stubFor(get(urlPathEqualTo("/api/users")) .withQueryParam("page", equalTo("1")) .withQueryParam("size", equalTo("10")) .willReturn(aResponse() .withStatus(200) .withHeader("Content-Type", "application/json") .withBody(""" { "page": 1, "size": 10, "total": 100, "users": [ {"id": 1, "name": "User 1"}, {"id": 2, "name": "User 2"} ] } """))); given() .port(8089) .queryParam("page", 1) .queryParam("size", 10) .when() .get("/api/users") .then() .statusCode(200) .body("page", equalTo(1)) .body("size", equalTo(10)); } } 2. Advanced Response Scenarios
// AdvancedStubbingTest.java import org.junit.jupiter.api.Test; import java.util.UUID; import static com.github.tomakehurst.wiremock.client.WireMock.*; import static io.restassured.RestAssured.given; import static org.hamcrest.Matchers.*; class AdvancedStubbingTest extends BaseWireMockTest { @Test void testDelayedResponse() { // Simulate slow API response stubFor(get(urlEqualTo("/api/slow-endpoint")) .willReturn(aResponse() .withStatus(200) .withFixedDelay(2000) // 2 seconds delay .withBody(""" { "message": "This response was delayed" } """))); given() .port(8089) .when() .get("/api/slow-endpoint") .then() .statusCode(200) .body("message", equalTo("This response was delayed")); } @Test void testDynamicResponse() { // Generate dynamic responses stubFor(post(urlEqualTo("/api/orders")) .willReturn(aResponse() .withStatus(201) .withHeader("Content-Type", "application/json") .withTransformerParameter("generatedId", UUID.randomUUID().toString()) .withBody(""" { "orderId": "{{randomValue length=8 type='ALPHANUMERIC'}}", "status": "CREATED", "timestamp": "{{now}}" } """))); given() .port(8089) .contentType("application/json") .body("{\"product\": \"Laptop\", \"quantity\": 1}") .when() .post("/api/orders") .then() .statusCode(201) .body("status", equalTo("CREATED")) .body("orderId", not(emptyString())); } @Test void testResponseTemplating() { // Use response templating for dynamic content stubFor(post(urlEqualTo("/api/greet")) .withRequestBody(matchingJsonPath("$.name")) .willReturn(aResponse() .withStatus(200) .withHeader("Content-Type", "application/json") .withTransformers("response-template") .withBody(""" { "greeting": "Hello, {{jsonPath request.body '$.name'}}!", "timestamp": "{{now format='yyyy-MM-dd HH:mm:ss'}}", "requestId": "{{request.headers.X-Request-Id}}" } """))); given() .port(8089) .contentType("application/json") .header("X-Request-Id", "test-123") .body("{\"name\": \"Alice\"}") .when() .post("/api/greet") .then() .statusCode(200) .body("greeting", equalTo("Hello, Alice!")); } @Test void testScenarioBasedStubbing() { // Simulate stateful API behavior stubFor(get(urlEqualTo("/api/stateful")) .inScenario("Stateful API") .whenScenarioStateIs("Started") .willReturn(aResponse() .withBody("First call")) .willSetStateTo("SecondCall")); stubFor(get(urlEqualTo("/api/stateful")) .inScenario("Stateful API") .whenScenarioStateIs("SecondCall") .willReturn(aResponse() .withBody("Second call")) .willSetStateTo("ThirdCall")); stubFor(get(urlEqualTo("/api/stateful")) .inScenario("Stateful API") .whenScenarioStateIs("ThirdCall") .willReturn(aResponse() .withBody("Third call")); // Reset scenario state if needed wireMockServer.setScenarioState("Stateful API", "Started"); } } 3. Error Scenarios and Fault Simulation
// ErrorStubbingTest.java import org.junit.jupiter.api.Test; import static com.github.tomakehurst.wiremock.client.WireMock.*; import static io.restassured.RestAssured.given; import static org.hamcrest.Matchers.*; class ErrorStubbingTest extends BaseWireMockTest { @Test void testErrorResponses() { // Simulate various HTTP error responses stubFor(get(urlEqualTo("/api/not-found")) .willReturn(aResponse() .withStatus(404) .withBody(""" { "error": "Resource not found", "code": "NOT_FOUND" } """))); stubFor(get(urlEqualTo("/api/server-error")) .willReturn(aResponse() .withStatus(500) .withBody(""" { "error": "Internal server error", "code": "INTERNAL_ERROR" } """))); stubFor(get(urlEqualTo("/api/unauthorized")) .willReturn(aResponse() .withStatus(401) .withBody(""" { "error": "Authentication required", "code": "UNAUTHORIZED" } """))); given().port(8089).when().get("/api/not-found").then().statusCode(404); given().port(8089).when().get("/api/server-error").then().statusCode(500); given().port(8089).when().get("/api/unauthorized").then().statusCode(401); } @Test void testNetworkFaults() { // Simulate network issues stubFor(get(urlEqualTo("/api/unreliable")) .willReturn(aResponse() .withFault(Fault.MALFORMED_RESPONSE_CHUNK))); stubFor(get(urlEqualTo("/api/connection-reset")) .willReturn(aResponse() .withFault(Fault.CONNECTION_RESET_BY_PEER))); stubFor(get(urlEqualTo("/api/random-data")) .willReturn(aResponse() .withFault(Fault.RANDOM_DATA_THEN_CLOSE))); // These will throw exceptions - handle appropriately in your tests } @Test void testRateLimitingSimulation() { // Simulate rate limiting stubFor(get(urlEqualTo("/api/rate-limited")) .willReturn(aResponse() .withStatus(429) .withHeader("Retry-After", "60") .withBody(""" { "error": "Rate limit exceeded", "retryAfter": 60 } """))); given() .port(8089) .when() .get("/api/rate-limited") .then() .statusCode(429) .header("Retry-After", "60"); } } WireMock in Spring Boot Applications
1. Spring Boot Test Configuration
// SpringWireMockConfig.java import com.github.tomakehurst.wiremock.WireMockServer; import com.github.tomakehurst.wiremock.client.WireMock; import org.springframework.boot.test.context.TestConfiguration; import org.springframework.context.annotation.Bean; import org.springframework.http.HttpStatus; import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig; @TestConfiguration public class SpringWireMockConfig { @Bean(initMethod = "start", destroyMethod = "stop") public WireMockServer wireMockServer() { WireMockServer server = new WireMockServer(wireMockConfig().port(8089)); // Configure default stubs configureDefaultStubs(server); return server; } private void configureDefaultStubs(WireMockServer server) { server.stubFor(WireMock.get(WireMock.urlEqualTo("/api/health")) .willReturn(WireMock.aResponse() .withStatus(HttpStatus.OK.value()) .withBody("{\"status\": \"UP\"}"))); } } 2. Service Class Under Test
// UserService.java import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import org.springframework.web.client.RestTemplate; @Service public class UserService { private final RestTemplate restTemplate; private final String userServiceUrl; public UserService(RestTemplate restTemplate, @Value("${user.service.url}") String userServiceUrl) { this.restTemplate = restTemplate; this.userServiceUrl = userServiceUrl; } public User getUserById(Long id) { String url = userServiceUrl + "/api/users/" + id; return restTemplate.getForObject(url, User.class); } public User createUser(User user) { String url = userServiceUrl + "/api/users"; return restTemplate.postForObject(url, user, User.class); } public void deleteUser(Long id) { String url = userServiceUrl + "/api/users/" + id; restTemplate.delete(url); } } // User.java public class User { private Long id; private String name; private String email; // Constructors, getters, setters public User() {} public User(Long id, String name, String email) { this.id = id; this.name = name; this.email = email; } // Getters and setters public Long getId() { return id; } public void setId(Long id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getEmail() { return email; } public void setEmail(String email) { this.email = email; } } 3. Spring Boot Integration Test
// UserServiceIntegrationTest.java import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.ContextConfiguration; import com.github.tomakehurst.wiremock.client.WireMock; import static com.github.tomakehurst.wiremock.client.WireMock.*; import static org.junit.jupiter.api.Assertions.*; @SpringBootTest @ContextConfiguration(classes = {SpringWireMockConfig.class}) class UserServiceIntegrationTest { @Autowired private UserService userService; @Autowired private WireMockServer wireMockServer; @Test void testGetUserById() { // Setup stub wireMockServer.stubFor(get(urlEqualTo("/api/users/1")) .willReturn(aResponse() .withStatus(200) .withHeader("Content-Type", "application/json") .withBody(""" { "id": 1, "name": "Test User", "email": "[email protected]" } """))); // Execute service method User user = userService.getUserById(1L); // Verify results assertNotNull(user); assertEquals(1L, user.getId()); assertEquals("Test User", user.getName()); assertEquals("[email protected]", user.getEmail()); // Verify WireMock received the request wireMockServer.verify(getRequestedFor(urlEqualTo("/api/users/1"))); } @Test void testCreateUser() { // Setup stub wireMockServer.stubFor(post(urlEqualTo("/api/users")) .withRequestBody(equalToJson(""" { "name": "New User", "email": "[email protected]" } """)) .willReturn(aResponse() .withStatus(201) .withHeader("Content-Type", "application/json") .withBody(""" { "id": 2, "name": "New User", "email": "[email protected]" } """))); // Execute service method User newUser = new User(null, "New User", "[email protected]"); User createdUser = userService.createUser(newUser); // Verify results assertNotNull(createdUser); assertEquals(2L, createdUser.getId()); assertEquals("New User", createdUser.getName()); // Verify WireMock received the request wireMockServer.verify(postRequestedFor(urlEqualTo("/api/users")) .withRequestBody(equalToJson(""" { "name": "New User", "email": "[email protected]" } """))); } } Advanced WireMock Patterns
1. Request Verification and Assertions
// VerificationTest.java import org.junit.jupiter.api.Test; import static com.github.tomakehurst.wiremock.client.WireMock.*; import static org.junit.jupiter.api.Assertions.*; class VerificationTest extends BaseWireMockTest { @Test void testRequestVerification() { // Setup stub stubFor(post(urlEqualTo("/api/audit")) .willReturn(aResponse().withStatus(200))); // Make multiple requests for (int i = 0; i < 3; i++) { given() .port(8089) .contentType("application/json") .body("{\"event\": \"test-event-" + i + "\"}") .when() .post("/api/audit"); } // Verify exact number of requests verify(exactly(3), postRequestedFor(urlEqualTo("/api/audit"))); // Verify request with specific body verify(postRequestedFor(urlEqualTo("/api/audit")) .withRequestBody(containing("test-event-1"))); // Verify request headers verify(postRequestedFor(urlEqualTo("/api/audit")) .withHeader("Content-Type", equalTo("application/json"))); } @Test void testRequestCapture() { // Setup stub stubFor(any(anyUrl()) .willReturn(aResponse().withStatus(200))); // Make various requests given().port(8089).when().get("/api/resource1"); given().port(8089).when().post("/api/resource2"); given().port(8089).when().put("/api/resource3"); // Capture and analyze all requests var allRequests = wireMockServer.getAllServeEvents(); assertEquals(3, allRequests.size()); // Analyze request methods long getCount = allRequests.stream() .filter(event -> event.getRequest().getMethod().getName().equals("GET")) .count(); assertEquals(1, getCount); } } 2. File-Based Stubbing
// FileBasedStubbingTest.java import org.junit.jupiter.api.Test; import java.io.File; import java.nio.file.Files; import java.nio.file.Paths; import static com.github.tomakehurst.wiremock.client.WireMock.*; import static io.restassured.RestAssured.given; class FileBasedStubbingTest extends BaseWireMockTest { @Test void testStubbingFromFiles() throws Exception { // Create mappings directory structure Files.createDirectories(Paths.get("src/test/resources/wiremock/mappings")); Files.createDirectories(Paths.get("src/test/resources/wiremock/__files")); // Create response body file Files.writeString(Paths.get("src/test/resources/wiremock/__files/user-response.json"), """ { "id": 123, "name": "File-based User", "email": "[email protected]" } """); // Create mapping file Files.writeString(Paths.get("src/test/resources/wiremock/mappings/user-mapping.json"), """ { "request": { "method": "GET", "url": "/api/file-user" }, "response": { "status": 200, "bodyFileName": "user-response.json", "headers": { "Content-Type": "application/json" } } } """); // WireMock will automatically pick up files from the classpath // when configured with fileSource given() .port(8089) .when() .get("/api/file-user") .then() .statusCode(200) .body("name", equalTo("File-based User")); } } 3. WireMock as Standalone Server
// StandaloneWireMockManager.java import com.github.tomakehurst.wiremock.WireMockServer; import com.github.tomakehurst.wiremock.client.WireMock; import com.github.tomakehurst.wiremock.core.WireMockConfiguration; import org.springframework.stereotype.Component; import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; import static com.github.tomakehurst.wiremock.client.WireMock.*; @Component public class StandaloneWireMockManager { private WireMockServer wireMockServer; @PostConstruct public void startServer() { wireMockServer = new WireMockServer( WireMockConfiguration.options() .port(8089) .usingFilesUnderClasspath("wiremock") ); wireMockServer.start(); WireMock.configureFor("localhost", 8089); setupDefaultStubs(); } @PreDestroy public void stopServer() { if (wireMockServer != null) { wireMockServer.stop(); } } private void setupDefaultStubs() { // Default health check stubFor(get(urlEqualTo("/health")) .willReturn(aResponse() .withStatus(200) .withBody("OK"))); // Default not found stubFor(any(anyUrl()) .atPriority(10) .willReturn(aResponse() .withStatus(404) .withBody("Not Found"))); } public void resetAll() { wireMockServer.resetAll(); setupDefaultStubs(); } public WireMockServer getServer() { return wireMockServer; } } Best Practices for WireMock
- Organize Stubs Logically: Group related stubs and use meaningful names
- Use Response Templating: For dynamic responses when needed
- Verify Interactions: Always verify that expected calls were made
- Clean Up: Reset WireMock between tests to avoid state leakage
- Use File-Based Stubs: For complex responses or when stubs are reused across multiple tests
// BestPracticesExample.java import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import static com.github.tomakehurst.wiremock.client.WireMock.*; import static org.junit.jupiter.api.Assertions.*; class BestPracticesExample extends BaseWireMockTest { @BeforeEach void setUp() { // Reset WireMock before each test wireMockServer.resetAll(); } @AfterEach void tearDown() { // Verify no unexpected interactions wireMockServer.verify(0, unexpectedRequests()); } @Test void testOrganizedStubbing() { // Group related stubs setupUserStubs(); setupOrderStubs(); // Test logic that uses both stubs // ... } private void setupUserStubs() { stubFor(get(urlPathMatching("/api/users/.*")) .willReturn(aResponse() .withStatus(200) .withBody("{\"id\": 1, \"name\": \"Test User\"}"))); } private void setupOrderStubs() { stubFor(post(urlEqualTo("/api/orders")) .willReturn(aResponse() .withStatus(201) .withBody("{\"orderId\": \"ORD-123\"}"))); } } WireMock provides a comprehensive solution for API stubbing in Java applications, enabling robust testing of HTTP interactions. By leveraging its rich feature set, you can create realistic API simulations that improve test coverage and application reliability.