Skip to content

File-Backed Responses

Some bodies don’t want to live inside a JSON file. A PDF export can’t be written as JSON at all; a 200 KB captured payload turns a mock into something nobody can read; a SOAP envelope hand-escaped into a JSON string is impossible to diff. response_file points a mock at a file next to it and serves that file’s exact bytes.

{
"method": "GET",
"path": "/reports/:id/export",
"status": 200,
"response_file": "fixtures/report.csv",
"response_headers": {
"Content-Disposition": "attachment; filename=\"report.csv\""
}
}
Terminal window
curl -i http://localhost:8080/reports/9/export
# HTTP/1.1 200 OK
# content-type: text/csv; charset=utf-8
# content-disposition: attachment; filename="report.csv"
#
# id,name,plan,seats,mrr
# 1,Acme Corp,enterprise,250,12500

Binary works the same way, byte for byte:

{
"method": "GET",
"path": "/users/:id/avatar",
"status": 200,
"response_file": "fixtures/avatar.png"
}

The path is resolved relative to the mock file’s own directory, not to the working directory — so a mocks tree stays relocatable and a Docker volume mount works unchanged:

mocks/
└── advanced/
├── get_report_export.json → "response_file": "fixtures/report.csv"
└── fixtures/
├── report.csv
├── invoice.xml
└── avatar.png

A path that resolves outside the mocks root — ../../etc/passwd, an absolute path, or a symlink pointing out of the tree — is refused at load time with an error naming the mock file, and that mock is not registered.

The first of these wins:

  1. Content-Type in the mock’s own response_headers;
  2. the file extension: .json, .xml, .csv, .html, .txt, .png, .jpg/.jpeg, .pdf, .zip;
  3. application/octet-stream.

A .json fixture is served as a JSON body, not as a JSON-quoted string.

Set "template": true to interpolate {{path.*}}, {{query.*}}, {{header.*}}, {{body.*}}, and {{faker.*}} inside the file — the same expressions a response supports (see Response Templating):

{
"method": "POST",
"path": "/soap/invoices/:id",
"status": 200,
"template": true,
"response_file": "fixtures/invoice.xml"
}
<InvoiceId>{{path.id}}</InvoiceId>
<Currency>{{query.currency}}</Currency>
<CustomerRef>{{body.customer_ref}}</CustomerRef>

Templating is off by default and never runs on a binary content type, so a PNG that happens to contain the bytes {{ is still a PNG.

A sequence step takes the same two fields, so a retry flow can end in a real file:

{
"method": "GET",
"path": "/flaky-export",
"status": 200,
"sequence": [
{ "status": 503, "response": { "error": "unavailable" } },
{ "status": 200, "response_file": "fixtures/report.csv", "repeat": true }
]
}
  • response and response_file are mutually exclusive. A mock setting both is a load error naming the file. Setting neither is unchanged behavior (response: null).
  • Files are read at load time and re-read on every hot reload cycle, so editing a fixture takes effect within ~2s without touching the mock file. Request handling never touches the disk.
  • MIMIC_MAX_RESPONSE_FILE (default 10 MB) caps one fixture. A file over the cap is reported and its mock is skipped rather than half-loaded; 0 removes the cap.
  • A .json fixture is not loaded as a mock. The loader reads every mock file under the mocks directory, but a file some mock claims as its response_file is served, never registered.
  • The request log stores a text body verbatim (truncated at MIMIC_MAX_RECORDED_BODY) and a binary body as a one-line descriptor — <70 bytes of image/png from fixtures/avatar.png> — so the dashboard stays readable.
  • Everything else composes as usual: path parameters, matchers, delay_ms, tags, and CORS all work with a file-backed body.