28 releases

Uses new Rust 2021

0.5.0-rc.1 Apr 14, 2022
0.4.14 Feb 22, 2022
0.4.13 Jan 12, 2022
0.4.12 Dec 20, 2021
0.3.4 Mar 23, 2021

#188 in Testing

Download history 14/week @ 2022-04-21 268/week @ 2022-04-28 188/week @ 2022-05-05 463/week @ 2022-05-12 127/week @ 2022-05-19 175/week @ 2022-05-26 257/week @ 2022-06-02 32/week @ 2022-06-09 35/week @ 2022-06-16 45/week @ 2022-06-23 71/week @ 2022-06-30 46/week @ 2022-07-07 54/week @ 2022-07-14 165/week @ 2022-07-21 35/week @ 2022-07-28 93/week @ 2022-08-04

378 downloads per month
Used in 2 crates




Wiremock rewritten in Rust

Extends wiremock-rs by supporting Wiremock json stubs as input.

Use it when you have to mock an external service over http and want a language agnostic format for representing your mocks. Especially shines when the service you have to mock already proposes and publishes Wiremock e.g. Spring Boot with Spring Cloud Contract.

You can use stubr-build to share stubs between a producer project and a consumer one.

Also available as a cli.

use it

First prepare some stubs

echo "{\"request\": {\"method\": \"GET\"}, \"response\": { \"status\": 200 }}" > tests/stubs/hello.json

Then use this stub in your tests.

use isahc;
use stubr::*;
use asserhttp::*;

#[stubr::mock] // <- you can also provide stubs path here e.g. #[stubr::mock("hello.json")]
async fn with_macro() {

async fn simple_async() {
    // supply a directory containing json stubs. Invalid files are just ignored
    let stubr = Stubr::start("tests/stubs").await;
    // or just mount a single file
    let stubr = Stubr::start("tests/stubs/hello.json").await;
    // or configure it (more configurations to come)
    let stubr = Stubr::start_with("tests/stubs", Config { port: Some(8080), ..Default::default() }).await;

fn simple_blocking() {
    // can also be used in a blocking way
    let stubr = Stubr::start_blocking("tests/stubs");
    let stubr = Stubr::start_blocking_with("tests/stubs", Config { port: Some(8080), ..Default::default() });

wiremock cheat sheet

This is a condensed reminder of Wiremock documentation regarding json stubs format. It is also a view of the currently implemented features in stubr : just things which actually work in stubr are present.

You can also get assistance for writing json stubs with IDE completion provided by stubr.

  "id": "82d86e05-9ee0-44ca-9a8d-1fc6f719437e", // (optional) unique stub identifier. Returned in 'Matched-Stub-Id' header
  "priority": 1, // (optional) helps solving interlaced conditions (many stubs match the request). 1 is the highest priority, 255 the lowest
  "request": {
    "method": "GET", // (optional) http method. Can be "ANY" to match any method. Defaults to "ANY"
    "urlPath": "/api/exact-url", // exact uri match
    "urlPathPattern": "/api/regex-url/([a-z]{4})", // uri must match regex
    "urlPattern": "/api/regex-url/([a-z]{4})\\?and=([a-z]{4})", // uri & query must match regex
    "url": "/api/url?age=young", // raw url + query parameters by equality matching
    "queryParameters": {
      "firstname": { "equalTo": "beltram" }, // by equality matching (can also be an int, or a boolean)
      "lastname": { "equalTo": "maldant", "caseInsensitive": true }, // case insensitve equality
      "age": { "absent": true } // must be absent
      "city": { "contains": "a" } // must contain the letter 'a'
      "title": { "matches": "([A-Za-z]+)" } // must match regex
      "job": { "doesNotMatch": "([A-Za-z]+)" } // or must not match regex
    "headers": {
      "Content-Type": { "equalTo": "application/json" } // by equality matching
      // .. then all matchers described above for query parameters are also applicable here
    "basicAuth" : { // exact Basic authentication matching
      "username": "user",
      "password": "pass"
    "jwtAuth": {
      "equalTo": "eyJhbGciOiJSUzI1NiJ9.e30.MBkQ..." // plain JWT token
      "alg": {
        "equalTo": "RS256", // JWT algorithm by equality matcher
        "oneOf": ["RS256", "HS256"] // JWT must contain one of these algorithms
      "payloadPatterns": [
        // all matchers available in 'bodyPatterns' ⬇️
    "bodyPatterns": [
      { "equalToJson": {"name": "bob"} }, // strict json request body equality
      { "equalToJson": {"name": "bob"}, "ignoreExtraElements": true }, // ignore extra json fields supplied in request body. Default to false.
      { "equalToJson": {"name": "bob"}, "ignoreArrayOrder": true }, // ignore array items order. Default to false.
      { "matchesJsonPath": "$.name" }, // must just match json path
      { "matchesJsonPath": "$.consoles[?(@.name == 'xbox')]" }, // must match json path + equality
      { "matchesJsonPath": "$.consoles[?(@.price > 200)]" }, // must match json path + bound
      { "expression": "$.name", "contains": "o" }, // must match json path + contain the letter 'o'
      { "expression": "$.user", "equalToJson": { "name": "bob" } }, // must match json path + be equal
      { "binaryEqualTo": "AQID" /* Base 64 */ } // byte array equality
  "response": {
    "status": 200, // (required) response status
    "fixedDelayMilliseconds": 2000, // delays response by 2 seconds
    "jsonBody": { // json response (automatically adds 'Content-Type:application/json' header)
      "name": "john",
      "surnames": [ "jdoe", "johnny" ]
    "body": "Hello World !", // text response (automatically adds 'Content-Type:text/plain' header)
    "base64Body": "AQID", // binary Base 64 body
    "bodyFileName": "tests/stubs/response.json", // path to a .json or .txt file containing the response
    "headers": {
      "Content-Type": "application/pdf" // returns this response header
    // ..now response templating
    // it uses handlebars and allows you to define dynamic response based upon the content of the request
    // it can be used in "jsonBody", "body", "bodyFileName" or "headers"
    "transformers": ["response-template"], // required to activate response templating
    "jsonBody": {
      "url-path-and-query": "{{request.url}}",
      "url-path": "{{request.path}}",
      "url-path-segments": "{{request.pathSegments.[1]}}", // returns 'two' given '/one/two/three' path
      "query": "{{request.query.kind}}", // returns 'comics' given '/api/books?kind=comics'
      "multi-query": "{{request.query.kind.[1]}}", // returns 'novel' given '/api/books?kind=comics&kind=novel'
      "method": "{{request.method}}", // http request method e.g. "POST"
      "header": "{{request.headers.Content-Type}}", // returns request header with given key
      "multi-header": "{{request.headers.cache-control.[0]}}", // returns first value of "cache-control" values
      "body": "{{request.body}}", // returns raw request body
      "from-request": "{{jsonPath request.body '$.name'}}", // takes field 'name' from json request body
      "now": "{{now}}", // current datetime (UTC)
      "now-fmt": "{{now format='yyyy/MM/dd'}}", // (1) with custom Java SimpleDateFormat
      "now-fmt-epoch": "{{now format='epoch'}}", // epoch in milliseconds
      "now-fmt-unix": "{{now format='unix'}}", // epoch in seconds
      "now-positive-offset": "{{now offset='3 days'}}", // human time positive offset
      "now-negative-offset": "{{now offset='-3 days'}}", // human time negative offset
      "now-with-timezone": "{{now timezone='Europe/Rome'}}",
      "number-is-odd": "{{isOdd 3}}", // or 'isEven'
      "string-capitalized": "{{capitalize mister}}", // or 'decapitalize'
      "string-uppercase": "{{upper mister}}", // or 'lower'
      "number-stripes": "{{stripes request.body 'if-even' 'if-odd'}}",
      "string-trim": "{{trim request.body}}", // removes leading & trailing whitespaces
      "size": "{{size request.body}}", // string length or array length
      "base64-encode": "{{base64 request.body padding=false}}", // padding is optional and defaults to true
      "base64-decode": "{{base64 request.body decode=true}}",
      "url-encode": "{{urlEncode request.header.x-raw}}",
      "url-decode": "{{urlEncode request.header.x-encoded decode=true}}",
      // you can also use 'any*' helpers. They will produce a random value
      "regex": "{{anyRegex '[a-z]{4}'}}", // generate a random string matching regex
      "string": "{{anyNonEmptyString}}", // or '{{anyNonEmptyString}}'
      "alphanum": "{{anyAlphaNumeric}}",
      "boolean": "{{anyBoolean}}",
      "uuid": "{{anyUuid}}",
      "ip": "{{anyIpAddress}}", // e.g. ''
      "host": "{{anyHostname}}", // e.g. 'https://github.com'
      "email": "{{anyEmail}}", // e.g. 'john.doe@gmail.com'
      "enum": "{{anyOf 'alpha' 'beta' 'gamma'}}", // returns randomly one of those 3 values
      "number": "{{anyNumber}}", // integer or float 
      "integer": "{{anyI32}}", // also all Rust int types (u32, u8, i64 etc..)
      "float": "{{anyFloat}}",
      "anyDate": "{{anyDate}}", // or 'anyTime', 'anyDatetime', 'anyIso8601'


Stubr can be used to record http traffic in your unit tests and dump them into json stubs. Currently, integration is quite limited but much more (actix, warp, rocket, tide) are around the corner.

The recorder acts as a standalone proxy server, so you need to configure your http client to use it.
You can use the record-isahc feature to get a configured isahc client with Stubr::record().isahc_client() or the record-reqwest feature to get a configured reqwest client with Stubr::record().reqwest_client(). Your stubs will then be stored under target/stubs/localhost

use stubr::Stubr;
use isahc;

// this requires `record` and `record-reqwest` (or `record-isahc`) features which are not default.

#[tokio::test(flavor = "multi_thread")] // required for recording
#[stubr::mock] // start a standalone http server to record, for example stubr itself
async fn sample_test() {
    // stubs will be created under `target/stubs`


~708K SLoC