Migrating from WireMock to Mockarty
If you already maintain WireMock stubs (@wiremock/wiremock, the
standalone JAR, or the Docker image), you can keep them working unchanged
on Mockarty. This guide covers the migration path step by step.
What changes
- Server URL — point your existing WireMock clients (the admin API,
/__admin/*) at your Mockarty host instead of the WireMock host. The
system under test calls the stubs under the namespace’s stub base URL,
http://<host>:5770/stubs/<namespace>— use that where you used the
WireMock base URL. The request journal reports paths relative to that
base, as WireMock does, soverify(getRequestedFor(urlEqualTo("/orders")))
keeps working. - Authentication — add the Mockarty API token (
X-API-Keyheader or
the SDK’s standard mechanism) so requests reach the admin server.
What stays the same
- Your stub JSON files — the WireMock 3.x JSON schema is accepted as-is.
- The
/__admin/*admin REST endpoints — Mockarty exposes them on the
same path. - The stub matching semantics — every officially documented matcher kind
is supported (see the table below).
Drop-in docker replacement (--wiremock-only)
Start mockarty with the --wiremock-only flag and the resulting
container behaves like a standalone WireMock server — /__admin/*
runs without auth, stubs are served at the root of the container’s URL,
and WireMock compatibility is enabled at startup for the default
namespace (sandbox), so your testcontainers client gets a working mock
server on first boot.
docker run --rm -p 8080:8080 mockarty/mockarty-cli:latest --wiremock-only
Or as a Java test fixture:
GenericContainer<?> mockarty = new GenericContainer<>("mockarty/mockarty-cli:latest")
.withCommand("--wiremock-only")
.withExposedPorts(8080)
.waitingFor(Wait.forHttp("/__admin/health"));
mockarty.start();
String adminUrl = "http://" + mockarty.getHost() + ":" + mockarty.getMappedPort(8080) + "/__admin";
What --wiremock-only changes:
/__admin/*no longer requires a Mockarty auth header (real
WireMock has no auth either).- The default namespace (
sandbox) auto-flips itswiremock-compat
setting totrueso you don’t need to PATCH the toggle after every
restart. - Stubs answer at the root (
http://<host>:8080/orders), exactly where the
system under test would call a WireMock container./api/*and
/__admin/*keep their own meaning. - All other admin features (UI, TCM, security scanner, A2A) still
load — the flag is additive, not subtractive. Migration paths
outside this doc keep working.
The flag is intended for ephemeral CI containers / testcontainers
tests. For long-running multi-tenant Mockarty deployments use the
per-namespace toggle below.
Enabling WireMock compatibility per namespace
The feature is off by default and enabled per namespace so you can
roll it out one team or pipeline at a time.
Turn it on for a namespace over the API:
curl -X PATCH "$MOCKARTY/api/v1/namespaces/$NS/settings/wiremock-compat" \
-H "Authorization: Bearer $MOCKARTY_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{"wiremock_compat_enabled": true}'
GET the same path to read the current state. Send
{"wiremock_compat_enabled": false} to turn it back off.
WireMock compatibility ships with the Mock module — it is the same stubs
your mocks already are, driven through WireMock’s client. With the toggle on
but the Mock module absent from your licence, /__admin/* answers
402 {"error":"WireMock compatibility is not included in your license", "feature":"mock"} — the toggle alone is not enough.
Once both are in place, the following endpoints become reachable for that
namespace:
| Method | Path | Description |
|---|---|---|
| GET | /__admin/health |
Health check |
| GET | /__admin/version |
Version handshake |
| POST | /__admin/reset |
Reset everything: stubs, journal and scenario state |
| POST | /__admin/settings |
Accepted and answered 200; Mockarty has no process-global stub settings, so the response lists the keys it ignored |
| GET / POST / DELETE | /__admin/mappings |
List / create / delete all stubs (DELETE removes persistent stubs too) |
| GET / PUT / DELETE | /__admin/mappings/{id} |
Read / update / delete a stub |
| POST | /__admin/mappings/import |
Bulk import (with OVERWRITE / IGNORE duplicate policy) |
| POST | /__admin/mappings/reset |
Remove all WireMock-imported stubs (persistent stubs survive) |
| POST | /__admin/mappings/save |
No-op (Mockarty persists by default) |
| GET / DELETE | /__admin/mappings/unmatched |
Stubs no journalled request ever matched / delete exactly those |
| POST | /__admin/mappings/find-by-metadata |
Filter stubs by metadata |
| POST | /__admin/mappings/remove-by-metadata |
Bulk-delete stubs by metadata |
| GET / DELETE | /__admin/requests |
Request journal |
| POST | /__admin/requests/reset |
Clear the journal |
| POST | /__admin/requests/find |
Filter journal entries by method, all four url* forms, named headers matchers and bodyPatterns |
| POST | /__admin/requests/count |
Count journal entries matching the same filters (behind verify(n, …)) |
| POST | /__admin/requests/remove |
Delete journal entries matching the same filters |
| GET / DELETE | /__admin/requests/{id} |
Read / delete one journal entry |
| GET | /__admin/requests/unmatched |
List unmatched requests |
| GET | /__admin/requests/unmatched/near-misses |
For each unmatched request, the stubs that came closest |
| POST | /__admin/near-misses/request |
The stubs closest to a request you describe |
| POST | /__admin/near-misses/request-pattern |
The logged requests closest to a request pattern |
| POST | /__admin/requests/remove-by-metadata |
Delete the journal entries served by stubs with the given metadata |
| GET | /__admin/scenarios |
List scenarios + current state |
| POST | /__admin/scenarios/reset |
Return all scenarios to Started |
| PUT | /__admin/scenarios/{name}/state |
Force a scenario state |
| POST | /__admin/recordings/start |
Start journal-based recording |
| POST | /__admin/recordings/stop |
Stop and snapshot recorded entries into stubs |
| GET | /__admin/recordings/status |
NeverStarted / Recording / Stopped |
| POST | /__admin/recordings/snapshot |
Snapshot the journal without stopping |
| ANY | /__admin/recordings/proxy/* |
Forward a request to the recording target and record the exchange |
| GET | /__admin/files |
List body files |
| GET / PUT / DELETE | /__admin/files/{name} |
Body file CRUD |
Supported matchers
Every documented WireMock matcher kind is supported:
| Matcher | Notes |
|---|---|
equalTo |
Case-sensitive; combine with caseInsensitive: true for case-insensitive |
equalToIgnoreCase |
Native case-insensitive equality |
contains / doesNotContain |
Substring containment |
matches / doesNotMatch |
Java-style regex |
equalToJson |
Structural equality; supports ignoreArrayOrder and ignoreExtraElements modifiers |
matchesJsonPath |
JSONPath expression must produce a non-empty result |
equalToXml |
XML equality of the element tree (whitespace between elements, comments and attribute order ignored); XMLUnit options below |
matchesXPath |
XPath subset: paths, attribute filters, indexed selectors, text() predicates |
binaryEqualTo |
Byte equality vs a base64 expected blob |
before / after / equalToDateTime |
The expected date is RFC 3339, "now" or "now +3 days"; the actual value is read as RFC 3339 / ISO 8601, or in actualFormat (a Java pattern such as dd/MM/yyyy, or epoch / unix). truncateExpected / truncateActual, expectedOffset / expectedOffsetUnit (seconds to years) and applyTruncationLast are honoured |
hasExactly |
Multi-value field equals exactly the listed predicates |
includes |
Multi-value field includes (at least) every listed predicate |
absent: true |
Field must be absent |
matchesJsonSchema |
Body must validate against an inline JSON Schema (draft 2020-12 by default, draft-07 with schemaVersion). A body that is not JSON simply does not match; a schema that cannot be compiled matches nothing and reports the authoring error |
and / or / not |
Logical combinators — every matcher above nests inside them, including matchesJsonSchema |
URL matching honours all five WireMock forms: url, urlPath,
urlPattern, urlPathPattern and urlPathTemplate (RFC 6570 {var}
placeholders become named path parameters). url is the whole URL: with a
query string (/orders?status=open) the request must carry exactly that
query — another value, an extra parameter or none does not match.
Beyond the URL and the matcher table, the request pattern also supports
method, headers, queryParameters, cookies, bodyPatterns,
basicAuthCredentials (translated to an exact Authorization: Basic …
check), formParameters (matched against an
application/x-www-form-urlencoded body), multipartPatterns (each part
of a multipart/form-data body checked by name, headers and
bodyPatterns, matchingType ANY or ALL), pathParameters (the
variables of a urlPathTemplate, each checked by its matcher) and
clientIp (the caller’s address as Mockarty sees it — behind a proxy,
the forwarded client address).
A near-miss distance runs from 0 (would have matched) to 1 (nothing in
common): the method, the URL (weighted double, closer when more path
segments agree) and each header, query parameter and body condition of
the stub count. Up to three near misses are returned, nearest first.
Matcher modifiers. Every WireMock modifier is applied:
caseInsensitive; the date options above; matchingType (ANY / ALL) on
multipart patterns; schemaVersion on matchesJsonSchema (V202012 — the
default — and V201909 validate as draft 2020-12, V7 / V6 / V4 as draft-07;
a $schema inside the schema wins); xPathNamespaces on matchesXPath;
the object form of matchesJsonPath / matchesXPath
({"expression": "$.status", "equalTo": "open"}), which selects a value
and checks it with the nested matcher; and XMLUnit’s options on
equalToXml:
enablePlaceholders— an expected value${xmlunit.ignore}accepts
anything,${xmlunit.isNumber}any number,${xmlunit.isDateTime}any
date,${xmlunit.matchesRegex(ORD-\d+)}a regular expression;
placeholderOpeningDelimiterRegex/placeholderClosingDelimiterRegex
change the${}delimiters;ignoreOrderOfSameNode— sibling elements may come in any order;exemptedComparisons— XMLUnit comparisons that are skipped, for example
["TEXT_VALUE", "SCHEMA_LOCATION"];namespaceAwareness: NONE— namespaces are not compared.
Namespace prefixes never matter in equalToXml, only the namespace
they stand for.
The translator returns a gaps array on import. Every unsupported or
unrecognised key above is reported there, so read it: each entry means
the imported stub is less strict than the one you wrote.
Admin endpoints we do not implement
POST /__admin/shutdown (instance lifecycle belongs to the deployment
owner). GET /__admin/requests carries the
canonical serve-event envelope (request{url, absoluteUrl, method, …},
response{status}, wasMatched) with the flat fields kept beside it; an
unknown matcher key in a journal filter is rejected with 400 rather than
silently ignored.
postServeActions with the webhook action fire after the stub responds
(non-webhook extensions are reported as import gaps). Other custom
post-serve extensions are stored for round-trip only.
Response definition
-
status,body,jsonBody,base64Body,headers— all supported. -
bodyFileName— a file uploaded withPUT /__admin/files/<name>. -
fixedDelayMilliseconds— a fixed delay before the answer. -
delayDistribution— a random delay drawn for every request, added to
the fixed one:{"type": "lognormal", "median": 90, "sigma": 0.4}
(optionally capped withmaxValue),{"type": "uniform", "lower": 300, "upper": 500}or{"type": "fixed", "milliseconds": 200}. -
chunkedDribbleDelay—{"numberOfChunks": 5, "totalDuration": 1000}
sends the body in five pieces spread over one second, the way a slow
network delivers it. The first piece arrives at once. -
proxyBaseUrl— the request is forwarded to the base URL with its path
and query:proxyBaseUrl: "https://api.example.com/v2"sends
/users/7?x=1tohttps://api.example.com/v2/users/7?x=1.
proxyUrlPrefixToRemovecuts a leading path first,
additionalProxyRequestHeadersadds headers (an API key, for example)
andremoveProxyRequestHeadersdrops caller headers. The target must be
reachable from the Mockarty server; private and loopback addresses are
refused unless the operator allows them. -
transformers: ["response-template"]— the body (including a
bodyFileNamefile) and the response headers are Handlebars templates
rendered for every request:- request data —
request.method,request.url,request.path,
request.pathSegments.[N],request.path.<var>(aurlPathTemplate
variable),request.host,request.port,request.scheme,
request.baseUrl,request.clientIp,request.body,
request.bodyAsBase64,request.headers.X(any letter case),
request.query.X,request.cookies.X;.firstand.[N]pick one
value of a repeated header or parameter; - blocks —
{{#each}}(withas |item index|,@index,@first,
@last),{{#if}}/{{else}},{{#unless}},{{#with}}; - helpers —
jsonPath,regexExtract(a third argument stores the
groups in a variable),randomValue length=N type='…'(NUMERIC,
ALPHABETIC, HEXADECIMAL, UUID),randomInt lower= upper=,
now offset='3 days' format='yyyy-MM-dd' timezone='…',
parseDate,date,size,base64, plus comparison and string
helpers (eq,and,or,not,upper,lower,split,join).
Date formats are Java patterns (dd/MM/yyyy HH:mm) orepoch/
unix.
{"request": {"method": "POST", "urlPathTemplate": "/orders/{id}"}, "response": {"status": 200, "transformers": ["response-template"], "jsonBody": {"id": "{{request.path.id}}", "items": "{{#each (jsonPath request.body '$.items') as |i|}}{{i.sku}} {{/each}}"}}} - request data —
-
fault— all four kinds supported:EMPTY_RESPONSE,
MALFORMED_RESPONSE_CHUNK,RANDOM_DATA_THEN_CLOSE,
CONNECTION_RESET_BY_PEER.
Recording
Start a recording with the real service as the target, send traffic
through the recording proxy, then stop — every recorded exchange becomes
a stub that answers what the real service answered (status, headers and
body):
curl -X POST "$MOCKARTY/__admin/recordings/start" \
-H "Authorization: Bearer $MOCKARTY_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{"targetBaseUrl": "https://api.example.com"}'
curl "$MOCKARTY/__admin/recordings/proxy/orders" \
-H "Authorization: Bearer $MOCKARTY_API_TOKEN"
curl -X POST "$MOCKARTY/__admin/recordings/stop" \
-H "Authorization: Bearer $MOCKARTY_API_TOKEN"
- Traffic is recorded through
/__admin/recordings/proxy/<path>, which
forwards to<targetBaseUrl>/<path>. repeatsAsScenarios(on by default): a request recorded several times
with different answers becomes a scenario that replays the answers in
order — the third call gets the third answer, later calls keep getting
the last one. Set it tofalseto keep only the first answer.allowNonProxied: truealso records requests your existing stubs
answered (with the answer that stub gives). Off by default.captureHeadersturns the listed request headers into matchers;
persist: truemarks the recorded stubs persistent.
Scenarios (stateful behaviour)
WireMock scenarios round-trip end-to-end:
scenarioName,requiredScenarioState,newScenarioStateon a stub
are honoured at request time.GET /__admin/scenarioslists scenarios discovered from imported stubs.POST /__admin/scenarios/resetreturns every scenario toStarted.PUT /__admin/scenarios/{name}/stateforces a state.
Persistent stubs
Stubs imported with persistent: true survive
POST /__admin/mappings/reset — matches WireMock 3.x semantics.
Out of scope
transformers: ["<custom Java class>"]— onlyresponse-template.POST /__admin/shutdown— instance lifecycle stays with the operator.
Step-by-step migration
-
Spin up Mockarty and create or pick the namespace that will host
the migrated stubs. -
Mint an API token in Mockarty (Settings → API Tokens) scoped to
that namespace. -
Enable WireMock compatibility for the namespace.
-
Repoint your clients: change the WireMock URL to your Mockarty
admin URL and add the API token header. -
Bulk import your existing stubs:
curl -X POST "$MOCKARTY_URL/__admin/mappings/import" \ -H "X-API-Key: $TOKEN" \ -H "Content-Type: application/json" \ --data-binary "@wiremock-stubs.json" -
Inspect the response for any
gapsentries — those are the only
places you may need to adjust the original stub JSON. -
Review the imported stubs in the Mockarty UI — they appear in the
namespace’s mock list with thewm_ID prefix and can be edited the
same way as native mocks.
Verifying the migration
Run the same WireMock-driven tests against the Mockarty URL. They should
pass with no further changes — every documented matcher kind, scenarios,
faults, body files and response templating are honoured. The gaps you
get at import time are the contract for what might behave differently.