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.