Using sample files for API testing

Three ways APIs receive files, with curl and fetch examples and the assertions that matter.

Published by SampleTestFiles. About 4 minutes to read.

An API can receive a file in three ways. Which one it uses decides what you send, what limits apply and what can go wrong. This guide shows each with curl, then the checks worth automating.

The examples assume you have downloaded a couple of samples, for instance the 100 KB PDF and the 100 KB JSON file.

1. Multipart form data

This is what browsers send from a form, and what most upload endpoints expect. Each field, including the file, is a separate part with its own headers.

curl -sS https://api.example.test/v1/documents \
  -H 'Authorization: Bearer YOUR_TOKEN' \
  -F 'file=@sample-pdf-100kb.pdf;type=application/pdf' \
  -F 'title=Quarterly report'

The @ tells curl to read the file. The optional ;type= sets the Content-Type of that part; leave it out and curl guesses from the extension. Do not set the overall Content-Type header yourself, because curl must add the boundary parameter.

2. Raw binary body

Some APIs, and object storage services in particular, take the file as the entire request body.

curl -sS -X PUT https://api.example.test/v1/objects/report.pdf \
  -H 'Content-Type: application/pdf' \
  --data-binary @sample-pdf-100kb.pdf

Use --data-binary, not -d. The plain -d option strips line breaks and will corrupt the file.

3. Base64 inside JSON

Some APIs accept only JSON, so files are embedded as base64 strings.

printf '{"filename":"report.pdf","content":"%s"}' \
  "$(base64 < sample-pdf-100kb.pdf | tr -d '\n')" > body.json

curl -sS https://api.example.test/v1/documents \
  -H 'Content-Type: application/json' \
  --data-binary @body.json

Base64 makes the payload a third larger than the file. A 100 KB file becomes about 133 KB of JSON, which is already over the 100 KB default body limit of several frameworks. Many "the file is under the limit but the API rejects it" reports are this.

From JavaScript

In a browser or in Node.js 18 and later, fetch and FormData build a multipart request:

import { readFile } from 'node:fs/promises';

const bytes = await readFile('sample-pdf-100kb.pdf');
const form = new FormData();
form.append('file', new Blob([bytes], { type: 'application/pdf' }), 'sample-pdf-100kb.pdf');
form.append('title', 'Quarterly report');

const response = await fetch('https://api.example.test/v1/documents', {
  method: 'POST',
  body: form,
});
console.log(response.status, await response.json());

As with curl, do not set the Content-Type header by hand.

What to assert

A status code of 201 proves very little. A thorough upload test also checks:

  • The response body. Does it report the size and type that you sent?
  • The stored bytes. Download the file through the API and compare its SHA-256 checksum with the original's. This is the only proof that the content survived.
  • Idempotency. If the API supports idempotency keys, send the same request twice and confirm that only one resource exists.
  • Authorisation. Repeat the request without credentials, and with another user's credentials, and expect 401 and 403 or 404.

Computing a checksum for comparison:

sha256sum sample-pdf-100kb.pdf
curl -sS https://api.example.test/v1/documents/123/content | sha256sum

Error cases

Each of these should produce a 4xx response with an error body in the API's normal format. A 500 is a bug.

CaseHow to send itExpected
Body too largeUse the 10 MB sample413
Wrong typeSend a PNG to a PDF-only endpoint415 or 400
Empty fileSend the zero-byte file400 or 422
Missing file partOmit the -F 'file=...' argument400 or 422
Malformed JSONTruncate a JSON sample with head -c400
Mismatched typeSend a PDF declared as image/png415 or 400

For the malformed case:

head -c 5000 sample-json-100kb.json > truncated.json
curl -sS -o /dev/null -w '%{http_code}\n' \
  -H 'Content-Type: application/json' \
  --data-binary @truncated.json https://api.example.test/v1/import

Testing with large JSON

For endpoints that accept JSON documents rather than files, size testing follows the same logic. The 100 KB JSON sample is exactly 102,400 bytes, the default limit of the Express JSON parser. The 1 MB sample exceeds most defaults and should be refused unless the limit has been raised on purpose.

To check how a parser copes with structure rather than size, use the nested configuration sample. It contains every JSON value type, escaped characters, an exponent, an empty object and an empty array.

Sample files as mock responses

The same files work in the other direction. While a back end is being built, a front end or a client library can be developed against static responses. The users JSON sample is an array of 50 objects in the shape of a typical list endpoint.

Any static file server will do. With Python installed:

python3 -m http.server 8000

Then request http://localhost:8000/sample-users.json from your client code.

In a collection runner

Tools such as Postman, Bruno and Insomnia attach files to multipart requests through their interface and can run the same request across a list of files. Keep the fixtures in the same repository as the collection, and refer to them by relative path, so that the collection runs unchanged on a colleague's machine and in CI.

Files used in this guide

Sample files referred to in this guide
FileFormatSizeContentsDownload
100 KB JSON samplesample-json-100kb.json JSON 100 KB102,400 bytes Records: 297 Download JSON
1 MB JSON samplesample-json-1mb.json JSON 1 MB1,048,576 bytes Records: 3,029 Download JSON
100 KB PDF samplesample-pdf-100kb.pdf PDF 100 KB102,400 bytes Pages: 19 Download PDF
Sample users JSON (array of 50 objects)sample-users.json JSON 14.3 KB14,664 bytes Records: 50 Download JSON
Nested JSON with every value typesample-nested-config.json JSON 829 B829 bytes Root type: Object Download JSON

Related guides