Chapter 21: jev-mock — a stunt double for Jev
jev-mock is a pretend System One endpoint for tests. It is a real HTTP/2
server with real TLS, listening on 127.0.0.1 behind a certificate
authority it made up a moment ago, and it answers however the test tells it
to. A client under test cannot tell it from the real thing, except that it
is free, instant and never in a bad mood unless you ask it to be.
It knows nothing about Postgres, jev-http or any other client: anything that speaks HTTP/2 over TLS can be tested against it.
Three ways to play the part
flowchart LR
C["client under test"] -->|"HTTP/2 + TLS"| M["MockJev"]
M -->|"Reply"| C
M --- R1["a closure:<br/>answer anything"]
M --- R2["replay::Replay:<br/>answer from a fixture"]
M --- R3["forward::Forward:<br/>ask the real API, record it"]
R3 -->|"real key added here"| J(("api.typesafe.ai"))
R3 -->|"fixture()"| F[("fixture JSON")]
F --> R2
- From a closure.
MockJev::start(|request| Reply::json(200, ...)). The closure sees eachRecordedrequest (method, path, headers, body) and returns aReply: a status, headers, a JSON body, and optionally a delay, a wait until N requests have arrived, or exact raw bytes. - From a recording.
replay::Replayanswers from a fixture, keyed by the exact request bytes. A request the fixture lacks gets a 400 that names it (error_typefixture_miss), andassert_complete, also run when the lastReplayhandle drops, fails the test. A changed request can never reach the network, and can never pass. - By recording.
forward::Forwardpasses each request to a real endpoint with the real API key and records the answer. The fixture it builds (fixture()) then replays exactly what the endpoint said to exactly those bytes. The client under test talks to the mock with any key; the real one is added byForwardand never recorded. It needs a multi-threaded tokio runtime.exchange(body)sends and records a request built elsewhere, without the mock. postjevsql'stools/record-gates --senduses both.
Aside. The mock is also a witness. It counts TCP accepts, TLS connections, requests abandoned before an answer (the client reset the stream), the most requests in flight at once, and the client's HTTP/2 PINGs. It can send GOAWAY on every connection, advertise a
SETTINGS_MAX_CONCURRENT_STREAMSlimit, or stop acknowledging PINGs on the next N connections, so a test can check that a client's keep-alive really notices a dead path.
Try it
jev-http's integration tests are the shortest real use:
cargo test -p jev-http --test mock
In a test of your own, on a tokio runtime:
let mock = jev_mock::MockJev::start(|_| jev_mock::Reply::json(200, answer)).await;
// point the client at mock.endpoint() and trust mock.ca_file()
assert_eq!(mock.requests().len(), 1);
For the people who maintain it
In this folder
| Path | What |
|---|---|
| src/ | The code: Chapter 21½. |
| Cargo.toml | Dependencies: hyper and hyper-util (server), tokio-rustls with ring, rcgen for the throwaway CA, tempfile for the CA file, serde_json, tokio with a multi-threaded runtime. |
| CLAUDE.md | Invariants for agents. |
← Previous: Chapter 20¾: jev-http/tests · Up: jevcrates · Next: Chapter 21½: inside jev-mock/src →