Skip to main content
Technical Systems

What Is a Mock Server? Controlling an API Dependency

Use simulated responses to test client behaviour, then verify the real relationship separately.

Understand mock servers through request matching, failure scenarios, isolation, request verification, contract alignment, and the limits of simulated API behaviour.

What Is a Mock Server? Controlling an API Dependency

A checkout team needs to show customers what happens when delivery pricing is unavailable. The live shipping service is healthy, and its sandbox does not offer a convenient way to trigger the required failure. Waiting for an outage would leave an important part of the checkout untested.

A mock server can stand in for that service and return a controlled response. The application sends an HTTP request to the substitute, which matches the request against configured rules and returns the selected status, headers, and body. It can also simulate timing or connection behaviour where the chosen tool supports it.

That control lets a team develop and test a specific scenario repeatedly. The result establishes how the application behaves against the simulation. Further evidence is needed to establish whether the real provider still behaves as the client expects.

Keep the substitute at a useful boundary

A networked mock server runs separately from the application and receives real HTTP requests. This can exercise request construction, routing to the configured endpoint, response handling, and related client code. Other tools intercept requests inside a browser or process; they can be useful too, but they exercise a different path.

Be explicit about which approach the test uses. If an HTTP library is replaced with a function returning an object, the test does not exercise that library’s actual serialization or transport behaviour. If a mock server receives a request over local HTTP, it still does not reproduce the production provider’s TLS setup or network route.

For the shipping example, direct the application’s configurable shipping endpoint to a test-owned mock. Keep that configuration outside the customer’s normal interface. The test should establish that the expected substitute is active and avoid accidentally making paid or state-changing calls to the real provider.

Testing a client against a mock server
Testing a client against a mock server Configure a scenario Application sends HTTP request Mock matches request Mock returns controlled response Application handles result Test checks behaviour and requests
The test controls the dependency response and verifies both the outgoing request and the application's reaction.

Match the request the client is meant to send

A rule that returns success for every request can hide a broken integration. If the client sends the wrong method or omits a required query parameter, the mock should not silently provide the expected response anyway.

WireMock’s request matching documentation describes matching methods, URLs, headers, and bodies. Choose conditions that represent the API agreement. Avoid requiring incidental details such as JSON property order when the provider’s contract does not depend on them.

For example, this illustrative WireMock mapping returns a known delivery option for one request path:

{
  "request": {
    "method": "GET",
    "urlPath": "/shipping/options/O-104"
  },
  "response": {
    "status": 200,
    "headers": { "Content-Type": "application/json" },
    "jsonBody": {
      "orderId": "O-104",
      "options": [
        { "service": "standard", "amountMinor": 700, "currency": "NZD" }
      ]
    }
  }
}

The mapping format follows WireMock’s stubbing model. It defines a response fixture, not an implementation of shipping policy. If the client must also send a country parameter or an authentication header, represent those requirements in the matching or request verification rather than assuming the path-only rule covers them.

Treat unmatched requests as useful evidence. Configure the test harness to report unexpected traffic and the closest relevant expectations. A test that shows a generic empty result after an unmatched request can conceal a client defect behind apparently valid fallback behaviour.

Give each scenario an observable purpose

Start with the behaviour the team needs to examine. For checkout, a successful delivery quote should display the option and amount. An unavailable quote should preserve the customer’s basket and explain whether checkout can continue. A malformed response should reach a deliberate boundary error path rather than render an invented price.

A compact scenario set might include:

Dependency behaviourApplication result to examine
Valid delivery optionCorrect service, price, and currency displayed
Empty options listDistinct no-options message
Rejected requestRelevant correction or escalation path
Temporary service failureBounded recovery and clear availability state
Delayed responseLoading state and deadline handling
Invalid response bodyControlled parsing or validation failure

Do not assume one status code has the same business meaning for every provider. Derive the response and expected handling from the actual API agreement. A mock is most useful when each scenario has an explicit consequence that a reviewer can recognise.

WireMock’s fault simulation guidance covers delays and transport faults as well as response failures. These are separate conditions: an HTTP error response means a response arrived, while a connection failure may leave the client without one. Test them separately if the application treats them differently.

A delayed response can make a loading state easy to inspect, but avoid assuming an exact wall-clock duration under every test environment. Use reasonable timing tolerances and check the behaviour that matters, such as whether the interface remains usable and stops waiting at its configured deadline.

Use the application’s actual HTTP client when testing status handling. JavaScript’s fetch() does not reject its promise merely because the response has an HTTP error status; the caller must inspect the response. Mozilla’s Fetch guide explains this distinction. A mock returning a JSON error body with status 503 should exercise that status check, rather than relying on the promise rejection path used for a network failure.

Test an unexpected body type separately when the integration can encounter one, such as an intermediary returning an HTML error page. That examines whether parsing failure preserves a useful error category instead of replacing the original failure with an unexplained exception.

Verify what happened after the first failure

Receiving a controlled error is only the beginning of many tests. A client may retry, return a cached result, queue work, or disable an action. Verify the relevant outgoing calls and state changes so a reassuring screen does not hide unintended activity.

For a retry scenario, establish how many attempts are permitted, which failures qualify, and how the overall deadline constrains them. A mock can record the requests and return a defined sequence, such as a transient failure followed by success. Keep the scenario state isolated so another test cannot consume the first response.

A timeout deserves particular care for operations with side effects. Returning no response does not establish that the provider rejected the operation. The distinction in why timeouts do not cancel work should shape the test: an ambiguous outcome may require reconciliation instead of another submission.

The mock does not need a complete payment or fulfilment engine to represent that uncertainty. A controlled accepted-but-unconfirmed scenario can exercise the application’s pending state and subsequent status lookup. Keep the test focused on observable behaviour instead of building a second business system inside the substitute.

Isolate state and make failures reproducible

Give tests ownership of their mappings, scenario state, and request records. Reset them at a reliable point or use separate instances where the runner permits it. A shared mutable mock can be as difficult to reason about as a shared development database if tests change one another’s assumptions.

Use distinctive synthetic identifiers for concurrent scenarios and ensure matching rules cannot accidentally select another test’s response. If broad and narrow rules overlap, understand the tool’s selection and priority rules. Record the selected scenario with failure evidence so an unexpected result can be traced back to its configuration.

Fixtures should contain the variation needed by the application. Include absent optional fields, empty collections, and relevant pagination behaviour when the client depends on them. A single large fixture containing every possible field can leave the application’s handling of ordinary incomplete data unexamined.

Be careful when deriving fixtures from recorded traffic. Remove credentials and personal data, and preserve the properties the test actually needs. Replacing every value indiscriminately can change the relationship being examined, while retaining a real token creates a separate data-handling problem.

Keep the mock connected to provider evidence

A schema can check the fixture’s structure, but agreement with a schema alone does not establish that the provider returns that response for the represented request. Review error bodies, required headers, field meanings, and interaction rules as well as successful JSON shapes.

Consumer-driven contract testing can connect exercised consumer expectations with provider verification. Pact’s workflow describes that relationship. An ordinary mock fixture is not automatically a verified contract merely because both client and server teams have seen it.

Retain selected real integration checks for the connections the mock does not represent: provider authentication, deployed routing, certificate trust, and representative responses from the actual API or its supported test environment. Browser clients may also need checks of cross-origin and credential behaviour through the deployed route. These differences explain why contract and integration tests contribute complementary evidence.

Assign ownership to fixtures when an API changes. A response field renamed in the provider should trigger review of mappings, client handling, and existing scenarios together. Otherwise a green test suite can preserve the old agreement indefinitely while the real connection has moved on.

A useful mock server makes a test’s assumptions visible and controllable. It allows the team to ask precise questions about client behaviour, including cases that a live dependency rarely produces on demand. Keep those questions tied to the API agreement, and use real-provider evidence where the simulated boundary no longer answers them.