whyhttp 0.1.0

HTTP mock server for use in tests. Starts a real server, expectations configured via fluent builder API, verified automatically on drop.
Documentation

whyhttp

HTTP mock server for use in tests. Starts a real server in a background thread, expectations are configured via a fluent builder API and verified automatically on drop.

Quick start

let server = whyhttp::Whyhttp::run();

server
    .when().path("/greet").method("GET")
    .should().header("x-token", "secret")
    .response().status(200u16).body("hello");

let resp = reqwest::blocking::Client::new()
    .get(format!("{}/greet", server.url()))
    .header("x-token", "secret")
    .send()
    .unwrap();

assert_eq!(resp.status(), 200u16);
assert_eq!(resp.text().unwrap(), "hello");

Routing vs. validation

Every expectation has two independent sets of matchers:

  • whenrouting matchers: select which expectation handles the request. The first expectation whose matchers all pass wins.
  • shouldvalidating matchers: run after routing. They never affect which response is returned — failures appear in the drop report.
let server = whyhttp::Whyhttp::run();

// Route only POST /orders, but assert the body separately.
server
    .when().path("/orders").method("POST")
    .should().body(r#"{"qty":1}"#)
    .response().status(201u16);

Drop behavior

When Whyhttp is dropped, all collected issues are printed and the test panics if anything is wrong — so a failing mock automatically fails the test. Possible issues:

Report Cause
NoCall Expectation was configured but never triggered
MismatchTimes Expectation was triggered the wrong number of times
UnmatchedRequest Request arrived but no expectation matched it
Matcher Routing matched, but a should matcher failed

Matchers

Both when (routing) and should (validating) support the same set:

Method Matches when…
path(p) path equals p
method(m) HTTP method equals m (case-insensitive)
query(k, v) query param k equals v
query_exists(k) query param k is present
without_query(k) query param k is absent
header(k, v) header k equals v
header_exists(k) header k is present
without_header(k) header k is absent
body(b) body equals b
without_body() body is empty

Call count assertion

let server = whyhttp::Whyhttp::run();

// This endpoint must be called exactly twice.
server.when().path("/api").should().times(2u16).response().status(200u16);