Why Mock APIs?

API mocking creates simulated versions of real APIs that return predefined responses. This is essential in modern development because:

Common Scenarios for Mocking

  1. Third-party API unavailability — the payment provider’s sandbox is down, but you need to test checkout
  2. API still in development — the backend team hasn’t finished the endpoint yet, but frontend needs to integrate
  3. Cost reduction — calling a paid API (Google Maps, OpenAI) thousands of times during testing is expensive
  4. Deterministic testing — real APIs may return different data each time; mocks return consistent data
  5. Edge case simulation — it’s hard to trigger a 500 error from a real API; mocks can simulate any response
  6. Performance testing — mock APIs can simulate slow responses, timeouts, and network errors
  7. Offline development — developers can work without internet connectivity

Stubs vs. Mocks vs. Fakes

TermDefinitionExample
StubReturns predefined responsesReturns {id: 1, name: "Alice"} for any GET /users/1
MockStub + verifies expectationsSame as stub, but also verifies the request was made exactly once
FakeSimplified working implementationIn-memory database instead of real PostgreSQL

Introduction to WireMock

WireMock is the most popular API mocking tool in the Java ecosystem, but it works with any language via its HTTP API or standalone server.

Running WireMock

Standalone (any language):

# Download
wget https://repo1.maven.org/maven2/org/wiremock/wiremock-standalone/3.3.1/wiremock-standalone-3.3.1.jar

# Run
java -jar wiremock-standalone-3.3.1.jar --port 8080

# WireMock is now running at http://localhost:8080

Docker:

docker run -d -p 8080:8080 wiremock/wiremock:latest

Creating Your First Stub

Via JSON mapping file (place in mappings/ directory):

{
  "request": {
    "method": "GET",
    "url": "/api/users/1"
  },
  "response": {
    "status": 200,
    "headers": {
      "Content-Type": "application/json"
    },
    "jsonBody": {
      "id": 1,
      "name": "Alice Johnson",
      "email": "alice@example.com",
      "role": "admin"
    }
  }
}

Via HTTP API:

curl -X POST http://localhost:8080/__admin/mappings -d '{
  "request": {
    "method": "GET",
    "urlPattern": "/api/users/[0-9]+"
  },
  "response": {
    "status": 200,
    "jsonBody": {"id": 1, "name": "Alice"}
  }
}'

Now GET http://localhost:8080/api/users/1 returns the mocked response.

Request Matching

WireMock supports sophisticated request matching:

URL Matching

{"url": "/api/users/1"}                    // Exact match
{"urlPattern": "/api/users/[0-9]+"}        // Regex match
{"urlPathPattern": "/api/users/.*"}        // Path pattern
{"urlPath": "/api/users", "queryParameters": {"role": {"equalTo": "admin"}}}

Header Matching

{
  "request": {
    "method": "GET",
    "url": "/api/data",
    "headers": {
      "Authorization": {"equalTo": "Bearer valid-token"},
      "Accept": {"contains": "application/json"}
    }
  }
}

Body Matching

{
  "request": {
    "method": "POST",
    "url": "/api/users",
    "bodyPatterns": [
      {"matchesJsonPath": "$.name"},
      {"matchesJsonPath": "$.email"}
    ]
  }
}

Advanced WireMock Features

Simulating Delays

{
  "request": {"method": "GET", "url": "/api/slow"},
  "response": {
    "status": 200,
    "fixedDelayMilliseconds": 3000,
    "jsonBody": {"data": "delayed response"}
  }
}

Simulating Errors

{
  "request": {"method": "GET", "url": "/api/error"},
  "response": {
    "status": 500,
    "jsonBody": {"error": "Internal Server Error"}
  }
}
{
  "request": {"method": "GET", "url": "/api/timeout"},
  "response": {
    "fault": "CONNECTION_RESET_BY_PEER"
  }
}

Stateful Behavior (Scenarios)

Mock different responses based on state:

[
  {
    "scenarioName": "User lifecycle",
    "requiredScenarioState": "Started",
    "newScenarioState": "User created",
    "request": {"method": "POST", "url": "/api/users"},
    "response": {"status": 201, "jsonBody": {"id": 1, "name": "Alice"}}
  },
  {
    "scenarioName": "User lifecycle",
    "requiredScenarioState": "User created",
    "request": {"method": "GET", "url": "/api/users/1"},
    "response": {"status": 200, "jsonBody": {"id": 1, "name": "Alice"}}
  }
]

Request Verification

Verify that expected requests were made:

# Verify a request was made
curl http://localhost:8080/__admin/requests/count -d '{
  "method": "POST",
  "url": "/api/users"
}'
# Response: {"count": 1}

# Get all recorded requests
curl http://localhost:8080/__admin/requests

Response Templating

Dynamic responses based on request data:

{
  "request": {"method": "GET", "urlPattern": "/api/users/(.*)"},
  "response": {
    "status": 200,
    "jsonBody": {
      "id": "{{request.pathSegments.[2]}}",
      "name": "User {{request.pathSegments.[2]}}"
    },
    "transformers": ["response-template"]
  }
}

Mocking Strategies

Service Virtualization Pattern

For microservice architectures, mock external dependencies:

[Your Service] → [WireMock: Payment API]
               → [WireMock: Email API]
               → [Real: Database]

Contract-First Mocking

Generate mocks from OpenAPI specifications:

# WireMock can auto-generate stubs from OpenAPI
java -jar wiremock-standalone.jar --enable-stub-cors --record-mappings

Mock in CI/CD

# GitHub Actions example
services:
  wiremock:
    image: wiremock/wiremock:latest
    ports:
      - 8080:8080
    volumes:
      - ./tests/mocks:/home/wiremock

steps:
  - name: Run integration tests
    run: npm test
    env:
      API_URL: http://localhost:8080

Alternatives to WireMock

ToolLanguageBest For
WireMockJava/AnyEnterprise, complex matching
MockServerJava/AnySimilar to WireMock
json-serverNode.jsQuick REST mock from JSON file
PrismAnyAuto-mock from OpenAPI spec
MSWJavaScriptBrowser/Node.js request interception
MountebankAnyMulti-protocol (HTTP, TCP, SMTP)

Hands-On Exercise

  1. Set up WireMock: Run WireMock standalone or via Docker. Create stubs for GET, POST, and DELETE operations.
  2. Request matching: Create stubs that respond differently based on query parameters, headers, and request body.
  3. Simulate failures: Create stubs for 500 errors, timeouts, and connection resets. Test how your client handles them.
  4. Verify requests: Use WireMock’s verification API to confirm that your client made the expected requests.
  5. Stateful mock: Create a scenario where POST creates a resource and subsequent GET returns it.

Key Takeaways

  • API mocking simulates real API behavior for testing when the actual API is unavailable, costly, or unpredictable
  • WireMock is the industry-standard mocking tool — it supports request matching, response templating, delays, faults, and stateful scenarios
  • Stubs return predefined responses; mocks add verification that expected requests were actually made
  • Use mocking for third-party APIs, APIs in development, edge case simulation, and CI/CD integration testing
  • Response templating and stateful scenarios enable realistic mock behavior that closely mimics real APIs