1# Chapter 21: jev-mock — a stunt double for Jev 2 3`jev-mock` is a pretend System One endpoint for tests. It is a real HTTP/2 4server with real TLS, listening on `127.0.0.1` behind a certificate 5authority it made up a moment ago, and it answers however the test tells it 6to. A client under test cannot tell it from the real thing, except that it 7is free, instant and never in a bad mood unless you ask it to be. 8 9It knows nothing about Postgres, jev-http or any other client: anything 10that speaks HTTP/2 over TLS can be tested against it. 11 12## Three ways to play the part 13 14```mermaid 15flowchart LR 16 C["client under test"] -->|"HTTP/2 + TLS"| M["MockJev"] 17 M -->|"Reply"| C 18 M --- R1["a closure:<br/>answer anything"] 19 M --- R2["replay::Replay:<br/>answer from a fixture"] 20 M --- R3["forward::Forward:<br/>ask the real API, record it"] 21 R3 -->|"real key added here"| J(("api.typesafe.ai")) 22 R3 -->|"fixture()"| F[("fixture JSON")] 23 F --> R2 24``` 25 261. **From a closure.** `MockJev::start(|request| Reply::json(200, ...))`. 27 The closure sees each `Recorded` request (method, path, headers, body) 28 and returns a `Reply`: a status, headers, a JSON body, and optionally a 29 delay, a wait until N requests have arrived, or exact raw bytes. 302. **From a recording.** `replay::Replay` answers from a fixture, keyed by 31 the exact request bytes. A request the fixture lacks gets a 400 that 32 names it (`error_type` `fixture_miss`), and `assert_complete`, also run 33 when the last `Replay` handle drops, fails the test. A changed request 34 can never reach the network, and can never pass. 353. **By recording.** `forward::Forward` passes each request to a real 36 endpoint with the real API key and records the answer. The fixture it 37 builds (`fixture()`) then replays exactly what the endpoint said to 38 exactly those bytes. The 39 client under test talks to the mock with any key; the real one is added 40 by `Forward` and never recorded. It needs a multi-threaded tokio 41 runtime. `exchange(body)` sends and records a 42 request built elsewhere, without the mock. postjevsql's 43 `tools/record-gates --send` uses both. 44 45> **Aside.** The mock is also a witness. It counts TCP accepts, TLS 46> connections, requests abandoned before an answer (the client reset the 47> stream), the most requests in flight at once, and the client's HTTP/2 48> PINGs. It can send GOAWAY on every connection, advertise a 49> `SETTINGS_MAX_CONCURRENT_STREAMS` limit, or stop acknowledging PINGs on the 50> next N connections, so a test can check that a client's keep-alive really 51> notices a dead path. 52 53## Try it 54 55`jev-http`'s integration tests are the shortest real use: 56 57``` 58cargo test -p jev-http --test mock 59``` 60 61In a test of your own, on a tokio runtime: 62 63```rust 64let mock = jev_mock::MockJev::start(|_| jev_mock::Reply::json(200, answer)).await; 65// point the client at mock.endpoint() and trust mock.ca_file() 66assert_eq!(mock.requests().len(), 1); 67``` 68 69## For the people who maintain it 70 71### In this folder 72 73| Path | What | 74| --- | --- | 75| [src/](src/) | The code: Chapter 21½. | 76| [Cargo.toml](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. | 77| [CLAUDE.md](CLAUDE.md) | Invariants for agents. | 78 79← Previous: [Chapter 20¾: jev-http/tests](../jev-http/tests/) · Up: [jevcrates](../) · Next: [Chapter 21½: inside jev-mock/src](src/) →