Skip to content
axxbetadocs
GitHub

Mock dependencies

View .mdOpen in Claude

Your service calls other services. In an acceptance test you replace them with WireMock mocks, which gives you two things: the responses are under your control, and you can check exactly what your service sent.

Stubs are ordinary WireMock mapping files. Axx does not create stubs; it verifies traffic. Mount the mappings into a WireMock container:

infra/wiremock/mappings/postcode-undeliverable.json
{
"name": "postcode-undeliverable",
"priority": 1,
"request": {
"method": "GET",
"urlPathPattern": "/v1/postcodes/[A-Z]{2}/999[^/]*"
},
"response": {
"status": 200,
"headers": { "Content-Type": "application/json" },
"body": "{\"postcode\": \"{{request.pathSegments.[3]}}\", \"country\": \"{{request.pathSegments.[2]}}\", \"deliverable\": false, \"reason\": \"no delivery to this postcode\"}",
"transformers": ["response-template"]
}
}
infra/compose.yaml (excerpt)
services:
address-service:
image: wiremock/wiremock:3.13.0
ports: ["8081:8080"]
volumes: ["./wiremock:/home/wiremock"]

Configure your service to call http://localhost:8081 (or the compose service name) instead of the real dependency.

Register the mock in the scenario, trigger the behavior, then check the requests WireMock received:

Feature: Address check
Background:
Given the parcels service with the following properties:
| url | http://localhost:8400 |
And the mocked addresses service with the following properties:
| url | http://localhost:8081 |
Scenario: The address service is asked with the API key
Given a POST request to /api/parcels
And a request payload using an application/json content example
And the request payload properties are:
| reference | PX-ADR-1101 |
| recipient.postcode | "53111" |
When the request is executed
Then the response status code is 201
And the mocked GET request to /v1/postcodes/DE/53111 named postcode-check was received by addresses
And the mocked request named postcode-check was received exactly 1 time
And the header X-Api-Key for mocked request named postcode-check on addresses is 'example-address-key'

the mocked GET request to /v1/postcodes/DE/53111 named postcode-check was received by addresses registers a request pattern (method and exact URL, including the query string) under a name you choose, and checks it was received at least once. Later steps refer to it by that name; the mock step reference has the ones that check counts, absence and headers.

Scenario: Invalid registrations never reach the address service
Given the OpenAPI validation levels are:
| validation.request.body.schema.minimum | IGNORE |
And a POST request to /api/parcels
And a request payload using an application/json content example
And the request payload properties are:
| reference | PX-ADR-1103 |
| weightGrams | 0 |
| recipient.postcode | "12489" |
When the request is executed
Then the response status code is 400
And the mocked GET request to /v1/postcodes/DE/12489 named skipped-check was not received

A mock that answers something the real API never would makes a test pass for the wrong reason, and a service that calls its dependency wrongly only finds out in production. The ghcr.io/nimbusxr/axx-wiremock image is WireMock with Axx’s OpenAPI validation extension. It checks every call to the mock against the dependency’s OpenAPI document: your service’s request, and the stub’s response.

infra/compose.yaml (excerpt)
services:
address-service:
image: ghcr.io/nimbusxr/axx-wiremock:0.1
ports: ["8081:8080"]
environment:
OPENAPI_SPEC_SOURCE: /var/openapi/address-service.yaml
OPENAPI_VALIDATION_MODE: report
volumes:
- ./wiremock:/home/wiremock
- ./openapi:/var/openapi:ro

OPENAPI_SPEC_SOURCE is the dependency’s document (a path inside the container or a URL). A stub can name another one with "metadata": {"openApiSpecSource": "..."}, or opt out with "openApiValidation": false. In report mode your service gets the stub’s answer unchanged, as it would from the real dependency, and Axx reports what broke the contract:

  • A mock step that checks the call fails, and names the rule:

    Terminal window
    ✗ And the mocked GET request to /v1/postcodes/DE/44444 named zone-lookup was received by addresses
    The GET /v1/postcodes/DE/44444 request named zone-lookup broke the contract of the mocked addresses service (/var/openapi/address-service.yaml):
    - validation.response.body.schema.additionalProperties (response): property 'surcharge' is not defined in the schema and the schema does not allow additional properties

    (response) means the stub broke the contract; (request) means your service did.

  • A call that breaks the contract and that no scenario checks fails the run, after the scenarios:

    Terminal window
    Run failures:
    ✗ Calls to the mocked addresses service (http://localhost:8081) broke its contract, and no scenario checked them: (mock)
    GET /v1/postcodes/DE/55555 (/var/openapi/address-service.yaml)
    - validation.response.body.schema.additionalProperties (response): property 'surcharge' is not defined in the schema and the schema does not allow additional properties

Axx only reads WireMock’s request journal, so scenarios running in parallel cannot affect each other’s checks. It reads the journals of the mocks the run’s scenarios register: register the mocked service in the Background of every feature whose scenarios make the calls, so Axx knows to look.

Every rule is an ERROR until you relax it. Relaxing is how you tell Axx a deviation is known: WARN logs the finding on the checking step instead of failing it, INFO and IGNORE drop it. The rules have the same keys as your own service’s contract (validation.request.body.schema.required, validation.response.status.unknown, …), and a key also covers the keys below it. The settings are separate: the OpenAPI validation levels are: never relaxes a dependency’s contract.

Relax where the deviation lives:

Where Relaxes Use it for
"openApiValidationLevels": {"validation.response.body": "WARN"} in a stub’s metadata the calls that stub answers, in every scenario and in the end-of-run check a stub whose answer is off-contract wherever it is used
OPENAPI_VALIDATION_LEVELS on the container, e.g. validation.response.body.schema.additionalProperties=WARN every call to that mock a known gap between the dependency’s document and its behavior
the scenario step below the calls that scenario’s mock steps check a scenario that exercises an off-contract call on purpose, next to the behavior it checks

In the example, the address service starts sending a field its document does not list yet, and quoting must keep working. The relaxation sits in that scenario, which checks the call:

Scenario: Quotes keep working when the address service sends a field it has not documented
Given the OpenAPI validation levels for the mocked addresses service are:
| validation.response.body.schema.additionalProperties | WARN |
And a POST request to /api/quotes
And a request payload using an application/json content example named 'Domestic parcel'
And the request payload property recipient.postcode is '"80995"'
When the request is executed
Then the response status code is 200
And the response payload property zone is 'DE-8'
And the mocked GET request to /v1/postcodes/DE/80995 named zone-lookup was received by addresses

The scenario step only reaches calls the scenario checks with a mock step: the end-of-run check cannot tell which scenario made a call. When a scenario relaxes a mocked service but checks none of its calls, Axx warns at the end of that scenario, and if one of its calls breaks the rule anyway, the run report names the scenario that meant to relax it.

The more specific place wins. Without the extension’s report mode (fail, its default), a call that breaks the contract also gets an HTTP 500 listing the findings; Axx reports it the same way. All settings, the admin endpoints and how the image is versioned are in the extension’s README.

Keep mock response bodies schema-valid as the provider’s API evolves with fixture factories: the json family can use an OpenAPI component as its schema, which is how the example generates postcode-remote.json from infra/wiremock/__files/postcodes.factory.yaml.