Most API testing content assumes you want a test platform — runners, assertions,
dashboards. But the testing most developers actually do, dozens of times a day, is
simpler: send a request, read the response, decide if it's right. .http files make that
manual loop fast, repeatable, and reviewable — and because the files live in Git, your
"tests" survive the person who wrote them.
This is a workflow guide for that loop. It's honest about scope: .http files carry no
assertions by themselves, and Karve has no test runner — at the end we cover when
and how to graduate to automation.
The unit of testing is a file, not a click
A request you typed into a GUI once is a test that exists only in your memory. The same
request as a ### block in orders.http is a test anyone can re-run, diff, and review:
@baseUrl = https://localhost:7220
### Create an order — happy path
POST {{baseUrl}}/api/orders
Content-Type: application/json
{ "customerId": 42, "items": [{ "sku": "KV-100", "qty": 2 }] }
### Fetch it back
GET {{baseUrl}}/api/orders/1001
Name blocks by intent ("happy path", "missing customer") — six months later, the name is the documentation. The format guide covers the syntax.
Build a smoke file per service
One smoke.http per service, containing the requests that prove it's alive: a health
check, an authenticated "who am I", and the one or two endpoints the business would
notice breaking. Keep it short enough to run top-to-bottom in under a minute — it's the
file you reach for after every deploy, and the first thing a new teammate runs. (The
health check and
JWT login recipes are ready-made starting blocks.)
Test the failures on purpose
The happy path tests itself every time you develop against it. What regresses silently are the error paths — so write them down deliberately:
### Missing required field → expect 400 with field errors
POST {{baseUrl}}/api/orders
Content-Type: application/json
{ "customerId": null }
### No token → expect 401
GET {{baseUrl}}/api/orders/1001
### Someone else's resource → expect 403
GET {{baseUrl}}/api/orders/9999
Authorization: Bearer {{limitedUserToken}}
The expected status lives in the block name — a convention, not an assertion, but one a human verifies at a glance. The validation errors recipe shows a fuller ASP.NET Core version of this pattern.
Vary the environment, not the file
Testing against dev, then staging, means swapping variable values — never editing
requests. Keep {{baseUrl}} and tokens in per-environment
.env files, and the same smoke.http runs
anywhere. Secrets follow the usual rule:
placeholders in the file, values in a git-ignored .env.
Where Karve fits in the loop
For this workflow, the client's job is speed and memory: Karve opens the same files from every repo in one native Windows workspace, switches environments, and keeps a persistent, searchable history of every send — so "what did this endpoint return before the change?" is a search, not an archaeology dig. What it deliberately doesn't do: assertions, scripted chains, or scheduled runs.
When manual isn't enough — graduate honestly
Manual .http testing catches what changed while you were looking. For what changes
while nobody is looking:
- run the same files in CI with the free runners — the
CI guide has working examples with
ijhttpandhttpyac, which do support test scripts and assertions; - put contract-level checks in code-level tests, where your real test framework lives.
The point of the file-based approach is that graduation is free: the smoke file you wrote by hand is the input to the CI runner. Nothing is rewritten; the manual loop and the automated one share one source of truth.
Related: store API requests in Git for repo layout,
and the examples gallery for fifteen reviewed .http starting points.