Testing

[ AxiomCore · Documentation ]

Mocks

Serve deterministic static, conditional, stateful and fault-injected responses from endpoint MockConfig declarations with axiom mock.

A contract mock answers requests from mock(MockConfig(...)) declarations on your endpoints, so you can build and test a client before the backend exists. axiom mock FILE.acore --profile contract starts the mock engine locally.

Pick a mock profile

CommandProfileServes
axiom mock FILE.acore --profile contractContractEndpoint MockConfig responses from source
axiom mock FILE.acoreauto (default)Full server simulation when compiling the source produces a private server plan; contract mocks otherwise
axiom mock --cloudCloud contract mocksMocks of the linked Cloud project; needs a linked project and a Cloud session
axiom serve FILE.acore --mockServer simulation onlyAlways requires a server plan and bound fixtures

auto never downgrades: a broken or unsupported server plan fails instead of falling back to contract mocks. axiom serve starts real backend execution, defaults to local axiom.acore, and never downloads Cloud mocks. With no input, axiom mock uses axiom.acore in the current directory. Cloud mocks keep the configuration cache and its ETag; see Cloud mocks.

Declare an endpoint response

axiom.acore (excerpt)
endpoint listTasks(method: GET, path: "/tasks") {
  response(type: Json)
  mock(MockConfig(
    fallback: MockResponse(
      status: 200,
      headers: { "content-type": "application/json" },
      data: StaticData(body: { tasks: [] })
    )
  ))
}
Terminal: project root
axiom mock axiom.acore --profile contract --port 8080 --debug

The server listens on 127.0.0.1:8080 by default (--host, --port). --debug prints redacted startup diagnostics, never raw credentials or request and response bodies.

RoutePurpose
Your endpointsMock responses
GET /axdoc, GET /axdoc/openapi.jsonGenerated API docs and OpenAPI
GET /_axiom/healthHealth check
POST /_axiom/state/resetRestore the initial state and reset sequential selection

On a loopback listener these routes are open. On a non-loopback listener the server issues a private X-Axiom-Sandbox-Key, writes it to an owner-only credentials.json reported at startup, and requires it on every route, docs, health and reset. Only the landing page at / stays open.

Responses and selection

MockResponse fieldDefaultMeaning
status, headers200, noneResponse status and headers
dataEmpty StaticDataThe body strategy (see below)
conditionsNoneAll must match for this response to be eligible
delayMs, delayJitterMs0Fixed and random added latency
weight1Used by weighted selection
labelNoneName used by state changes

MockConfig has enabled (default true), responses, selectionStrategy (first_match by default, or random, sequential, weighted), fallback, state and fault.

Acore excerpt: the responses argument of MockConfig(...)
MockConfig(responses: [
  MockResponse(
    label: "missing",
    conditions: [PathCondition(key: "id", op: "eq", value: "404")],
    status: 404
  )
])
ConditionInspects
HeaderCondition(key, op, value)A request header (default op exists)
QueryCondition(key, op, value)A query parameter
PathCondition(key, op, value)A path parameter
BodyCondition(path, op, value)A value in the JSON body
StoreCondition(key, op, value)Mock state (default op gt)
AllCondition, AnyCondition, NotConditionCombine other conditions

Condition operators are eq, neq, exists, missing, contains, matches, gt and lt.

Data strategyProduces
StaticData(body)A fixed body
TemplateData(body)A body with placeholders such as {{ request.path.id }}, {{ request.body.title }}, {{ store.tasks }} or generated {{ faker.uuid }} values
CollectionData(item, count, paging)A list of count items (default 20); PagingConfig reads limit and offset
EchoData(body)Values copied from the request or state
StoreQueryData(key, filterKey, filterValue, returnAll)Items read from mock state

Generated faker values change on every request. Use StaticData when a test asserts exact values.

Stateful behavior

Acore excerpt: the state argument of MockConfig(...)
MockConfig(state: MockStateConfig(
  initial: { tasks: [] },
  onReceive: [StoreMutation(type: "append", key: "tasks", value: { id: "1" })]
))
  • StoreMutation types are increment, increment_fields, set, update, update_all, append and delete, with optional by, filterKey and filterValue.
  • Mutations run in declaration order.
  • MockStateConfig.changes holds conditional StateChange entries. Each can match a selected response by responseLabel, test when conditions, apply mutations and append events to the activity list named by activityKey (default activity). Every matching change runs in order, so a later change can read state an earlier one wrote.
  • State is shared and in memory. Reset it with POST /_axiom/state/reset or a restart when a test needs isolation.

Fault injection

Acore excerpt: the fault argument of MockConfig(...)
MockConfig(fault: FaultConfig(
  errorRate: 0.1,
  errorResponse: MockResponse(status: 503),
  networkFault: NetworkFault(type: "slow_response", probability: 0.1, delayMs: 500)
))
NetworkFault typeBehavior
slow_responseAdds delayMs before responding
timeoutWaits 30 seconds, then returns 408
connection_resetReturns 502

errorRate and probability must be between 0 and 1 (probability defaults to 1). Fault injection shows how a consumer handles failures; it does not estimate a production service's reliability.

Reload on save

Terminal: project root
axiom mock axiom.acore --profile contract --watch --port 8080

--watch needs .acore source and a fixed loopback port. Invalid source keeps the last valid mock running; a successful reload starts with fresh state. A reload cannot switch profiles, so a change that adds a private server plan needs a restart. Cloud and artifact inputs do not support --watch.

Checklist

  • Inspect the evaluated contract (axiom eval) before starting the server.
  • Test response selection and the fallback explicitly.
  • Reset state between tests that need isolation.
  • Keep real credentials out of headers and templates.
  • Run the same client suite against the real backend before release.

Next steps