Mock file format
A mock is one text file that says which requests it answers and what it answers with. This page is the complete set of rules: you should be able to write a valid mock from it without reading any code.
At a glance
# Captured from api.example.com/app/version on 2026-09-29.
name: GET /app/version
match:
method: GET
path: /app/version
delayMs: 800
---
HTTP 200
content-type: application/json
{
"latest": "2.35.5",
"minimum": "2.32.2"
}From top to bottom:
- The rule, in YAML: which requests this mock answers, and a comment saying why it exists.
- A line that is exactly
---. - The status line,
HTTP 200. - Response headers, one
name: valueper line. - An empty line.
- The body, exactly as it should reach the app.
Parts 3 to 6 are written the way curl -i prints a response.
Where mocks live
- Mocks live in
.dartmole/mocks/at the root of the app's repository. - A mock is a file ending in
.mock. The loader ignores every other file in the folder, soREADME.mdor a body file can sit next to the mocks. - The id of a mock is its filename without
.mock:checkout-declined.mockhas the idcheckout-declined. Whether a mock is switched on is remembered on this machine, by id, so renaming a file switches that mock off. - Mocks are inert when they arrive. Nothing in the file says whether it is on.
- The file is UTF-8. Line endings can be LF or CRLF.
The rule
The rule is everything above the first line that is exactly --- (no
leading or trailing spaces). It is a YAML mapping, so # comments work there
and are the place to say why a mock exists. The file starts directly with the
YAML; do not open it with a --- line of its own, as Jekyll-style front matter
does, because that line is taken to be the separator.
| Key | Type | Default | Meaning |
|---|---|---|---|
name |
string | the id | What the desktop app shows for this mock. |
match.method |
string | any method | GET, POST… Compared case-insensitively. |
match.host |
string | any host | A host, optionally with * wildcards. No port and no scheme. |
match.path |
string | any path | A path, optionally with * wildcards. No query string. |
match.query |
mapping | any query | Query parameters the request must have. See Query parameters. |
delayMs |
integer ≥ 0 | 0 |
How long to wait before answering, in milliseconds. |
bodyFile |
string | — | Read the body from this file instead. See Bodies from a file. |
Every key is optional, and an empty rule is valid (it matches every request).
Unknown keys are ignored so that a newer file still loads in an older Dartmole.
Mind the spelling: a misspelt mach: is ignored like any unknown key, and the
mock then matches every request.
Matching
- An omitted field matches anything. Leave
hostout and the mock answers on production, staging, dev and localhost alike. That is usually what you want; name a host only when you mean just that one. *matches zero or more characters, anywhere, including/and.. There is no**.- Host and path are compared case-insensitively.
- Ports are not part of the host:
localhostmatcheslocalhost:8080andlocalhost:3000. pathnever includes the query string.path: /searchmatches/search?q=cats; to requireq, usequery.
host: api.example.com # that host only
host: "*.example.com" # subdomains, not the apex
host: "*example.com" # the apex too, since * also matches nothing
path: /v1/users/* # /v1/users/42, /v1/users/42/orders, and /v1/users/Quote any value that starts with *, because an unquoted * means something
else in YAML.
Query parameters
match:
path: /api/v2/tasks
query:
page: "2" # page must be 2
search: "*" # search must be there, with any value
sort: "name*" # sort must start with name
tag: [urgent, me] # tag must appear with both values- Each listed parameter must be present with a matching value. Parameters
not listed are ignored, so the mock above also answers
?page=2&search=x&sort=name&tag=me&tag=urgent&size=20. - No
querymatches any query string, including none. *in a value is the same wildcard as in a path, and"*"on its own accepts any value, including an empty one, as long as the parameter is there.- A name with a list of values requires each of them, in any order.
- Names and values are compared case-sensitively, because a query value
is data:
q: catdoes not match?q=Cat. They are compared after decoding, soq: black catmatches?q=black%20catand?q=black+cat. - Quote numbers and anything starting with
*. A bare20is read as the text20anyway, but"2"is what Dartmole writes.
When Dartmole captures a call, it writes none of the call's query parameters,
because a query string is where tokens tend to be. The editor offers them as
suggestions, and marks a name that looks like a credential, such as
access_token, apiKey or sig.
When several mocks match
When several switched-on mocks match one request, the most specific one answers. Mocks are compared on:
- how many fields they set, counting each query parameter as one,
- how many of those fields have no
*, - how many characters of those fields, and of query names, are not
*.
So /v1/users/42 beats /v1/users/*, which beats /v1/*; ?page=2 beats
?page=*, which beats no query at all. If two mocks are
still level after all three, that is a conflict: Dartmole answers with
neither, lets the request through to the real backend, and shows the conflict.
File order never matters.
The response
Everything below the --- line is the response.
Status line
The first line after --- is the status line:
HTTP 200Write it as HTTP followed by a space and a status code between 100 and
599. So that curl -i output can be pasted in unchanged, a version and a
reason phrase are also accepted and ignored: HTTP/1.1 404 Not Found and
HTTP/2 200 both work. Dartmole always writes the short HTTP 200 form.
Headers
Each following line is a response header, name: value, until the first empty
line.
- Names are case-insensitive. Dartmole writes them in lowercase.
- The value is everything after the first
:, with spaces at either end removed. - A name may appear more than once, and each line is sent in the order written.
set-cookieneeds this. - Continuation lines, which start with a space or tab, are not supported and are an error.
- There are no comments here. A line starting with
#is an error, because it is not a header. content-length,content-encoding,transfer-encoding,connectionandkeep-aliveare dropped: Dartmole sends the body uncompressed and works out its length itself. Pasted output often containscontent-encoding: gzipalthough the text is already decompressed, and keeping that header would make the app try to decompress plain text.
Dartmole leaves credential headers such as authorization and set-cookie out
of a mock it captures. A mock you write by
hand can include them.
Body
Everything after the first empty line is the body, byte for byte, with one
exception: exactly one newline at the very end of the file is removed,
because editors add one. That newline is \n, or \r\n in a CRLF file.
- No escaping, no indentation and no quoting. Paste the payload in as it is.
- To send a body that ends in a newline, leave an empty line after it.
- CRLF line endings inside the body are kept as written.
- The body is not checked against
content-type. A mock that returns broken JSON is a legitimate way to test how an app copes. The desktop editor warns when a body labelledapplication/jsondoes not parse, and nothing else. - For an empty body, end the file after the header lines. The empty line can be there or not.
Bodies from a file
A body that is not UTF-8 text, such as an image, or one too large to read
comfortably next to the rule, goes in a separate file named by bodyFile in
the rule:
name: Product image
match:
path: /images/product/*
bodyFile: product-image.png
---
HTTP 200
content-type: image/png- The path is relative to
.dartmole/mocks/and must stay inside it: no absolute paths and no... - The file is sent byte for byte, with nothing removed from the end.
- When
bodyFileis set, the response must end after its headers. A body written below them as well is an error, rather than one of the two winning silently. - When Dartmole captures a binary body it names the file after the mock:
product-image.pngforproduct-image.mock. - Any file named
<id>.<something>belongs to that mock. Dartmole deletes it along with the mock, and replaces it whenever the mock is saved from the editor, which writes a text body inline. Give a body file you maintain by hand a name of its own, such asproduct-image-body.png.
Examples
A JSON response with a delay, to see a loading state:
name: Slow profile
match:
method: GET
path: /v1/me
delayMs: 2000
---
HTTP 200
content-type: application/json
{"id": 42, "name": "Ada"}No body:
match:
method: DELETE
path: /v1/sessions/*
---
HTTP 204An error page that is not JSON:
# What the load balancer returns while a deploy is in progress.
name: Maintenance page
match:
path: /api/*
---
HTTP 503
content-type: text/html; charset=utf-8
retry-after: 120
<!doctype html>
<h1>Back soon</h1>Malformed JSON, on purpose:
# The body is cut off, as when a connection drops mid-response.
name: Truncated orders
match:
path: /v1/orders
---
HTTP 200
content-type: application/json
{"orders": [{"id": 1}, {"id":One page of a list, while every other page still comes from the backend:
# Page 2 is where the app's infinite scroll broke.
name: Tasks, page 2
match:
method: GET
path: /api/v2/tasks
query:
page: "2"
---
HTTP 200
content-type: application/json
{"items": [], "page": 2, "last": true}Two cookies:
match:
method: POST
path: /login
---
HTTP 200
set-cookie: session=abc; Path=/; HttpOnly
set-cookie: theme=dark; Path=/Errors and warnings
A mock that cannot be read is not skipped without a word. The desktop app lists it under "could not be read", with its filename and the first thing that is wrong. The line number counts from the top of the file.
Errors. Dartmole does not load the mock.
| Problem | Message |
|---|---|
No line that is exactly --- |
no "---" line: the response goes below one |
| The rule is not valid YAML | line N: the rule is not valid YAML: <reason> |
| The rule is YAML but not a mapping | the rule must be keys and values, like "name: …" |
delayMs is not a whole number ≥ 0 |
line N: "delayMs" must be a whole number of milliseconds |
match is not a mapping |
line N: "match" must be keys and values, like "path: /v1/users" |
match.query is not a mapping |
line N: "query" must be names and values, like "page: \"1\"" |
| A query parameter with no value | line N: query "<name>" needs a value, or "*" for any value |
| A query value that is not text | line N: query "<name>" must be text, or a list of text |
name, a match field or bodyFile is not text |
line N: "<key>" must be text |
Nothing after --- |
line N: expected "HTTP <status>" after "---" |
| Bad status line | line N: expected "HTTP <status>", got "<line>" |
| Status outside 100–599 | line N: status <code> is not between 100 and 599 |
A header line without : |
line N: expected "name: value", got "<line>" |
| A continuation line | line N: header lines cannot start with a space |
bodyFile missing |
bodyFile "<path>" does not exist |
bodyFile outside the folder |
bodyFile must be inside .dartmole/mocks/ |
bodyFile and a written body |
line N: remove the body or the bodyFile, not both |
| The file is not UTF-8 | not UTF-8 text; put binary bodies in a bodyFile |
A number or true where text is expected is read as that text, so name: 404
is the name 404.
Warnings. The mock loads and works. The desktop app does not show them yet, apart from the JSON warning, which the editor shows while you type.
| Problem | Message |
|---|---|
| An unknown key | line N: unknown key "<key>" is ignored |
An empty match |
matches every request |
| A dropped header | line N: "<name>" is dropped; Dartmole sets it itself |
| A JSON body that does not parse | body is labelled JSON but does not parse |
A rule that begins with a --- line, as Jekyll-style front matter does, has
no rule of its own. It surfaces as a bad status line (got "name: …"), so that
message should also say "the file starts with YAML, not with ---".
Compatibility
- The format only gains things. New keys are optional, and a mock that loads today will load in every later version.
- There is no
version:key. If a change ever cannot be added this way, it has to be recognisable from the file itself, for example by a new extension.