Quick answer: Mocking JSON endpoints is straightforward, but mocking file downloads exposes a different set of assumptions: the response is a binary stream, the headers carry as much of the contract as the body, and clients behave differently depending on content length, encoding, and whether the download is chunked or interrupted. A mock that returns a base64 string or a tiny placeholder file will make the happy path pass while leaving the real failure modes—large files, partial downloads, and correct content-type negotiation—completely untested. The reliable approach is to serve real fixture bytes with the exact headers a production object store or file server returns, including content-length and content-disposition, and to add explicit cases for resumable and failed downloads. If your mock only proves the client can receive a small text blob, the first large PDF or image export in production is still an untested path.
Most mock servers are optimized around JSON because that is the common API case, and a binary response is awkward to fit into the same inline-body model. Teams often work around it by embedding a short base64 string, which tests that the client requests the endpoint but not that it correctly decodes bytes, writes a file, or handles the headers that drive a browser or SDK download behavior. The endpoint returns 200, so the test goes green, while the actual download logic remains unverified.
What a binary mock must reproduce
The contract for a download is distributed across headers and body, and missing any piece changes client behavior.
| Element | Why it matters | Mock requirement |
|---|---|---|
| Content-Type | Drives handling | Exact type, e.g. application/pdf |
| Content-Disposition | Filename and inline/attachment | Set and asserted |
| Content-Length | Progress and buffering | Real byte length |
| Body bytes | Decoding correctness | Actual fixture file |
| Transfer encoding | Chunked vs known length | Both cases covered |
| Accept-Ranges | Resumable downloads | Range support |
The Content-Length row is where placeholder mocks fail quietly: a two-byte placeholder reports a trivial length, so progress bars, buffer sizing, and large-file memory behavior never get exercised against realistic sizes.
Serve real fixture bytes with exact headers
Point the mock at an actual fixture file and return its raw bytes with the headers the production endpoint would set, rather than embedding a base64 string in the body definition.
Using a real file also lets you assert the round trip: write the downloaded bytes to a temp path and compare them byte-for-byte against the fixture, which catches any accidental text decoding or base64 wrapping that would corrupt the result.
Test ranges, large files, and interruption
The production failures for downloads are about size and interruption, so model them explicitly: honor range requests for resumable downloads, serve a large fixture to exercise streaming and memory, and simulate a dropped connection partway through.
For the interruption case, close the connection after sending a partial body and confirm the client detects the short read against the expected content length and retries or resumes rather than writing a corrupt file and reporting success.
Derive the mock from the contract
When the response schema and headers are defined in the OpenAPI contract, a generated mock can return typed binary fixtures and the correct headers consistently, including the declared content types and response codes. This keeps the download mock aligned with the real endpoint and lets the same contract drive generated clients that already know how to stream bytes to disk.
The practical rule is to treat file downloads as a first-class contract rather than a quirky endpoint. Serve real fixture bytes with exact content type, disposition, and length; cover ranges, large files, and interrupted streams; and verify the downloaded bytes match the source. A mock that only returns a small base64 placeholder proves the endpoint exists, not that the client can actually download a file.
Frequently asked questions
Why not just return a base64 string from the mock?
That returns text the client may fail to decode correctly, and it skips the headers and realistic byte length that drive download behavior. Serve raw fixture bytes with the production headers instead.
Which headers matter most for a download?
Content-Type, Content-Disposition for the filename and mode, and Content-Length for progress and buffering. Range and Content-Range matter for resumable downloads.
How do I verify the client decoded the file correctly?
Write the downloaded bytes to a temp file and compare them byte-for-byte against the fixture. This catches text decoding errors and accidental base64 wrapping.
What failure cases should the mock cover?
Large files for streaming and memory, range requests for resumable downloads, and a connection dropped mid-stream to confirm the client detects a short read and retries rather than saving a corrupt file.
How does the API contract help with binary mocks?
It declares the response type and headers, so a generated mock serves typed binary fixtures with correct headers and generated clients know how to stream the bytes to disk.
This is part of the Powerduck vs Mockoon comparison — start there for the full picture of realistic mocking.
Top comments (0)