Syntax Sugar: prop_compose!

Defining strategy-returning functions like this is extremely useful, but the code above is a bit verbose, as well as hard to read for similar reasons to writing test functions by hand.

To simplify this task, proptest includes the prop_compose! macro. Before going into details, here’s our code from above rewritten to use it.

use proptest::prelude::*;

// snip

#[derive(Clone, Debug)]
struct Order {
  id: String,
  // Some other fields, though the test doesn't do anything with them
  item: String,
  quantity: u32,
}

fn do_stuff(order: Order) {
    let i: u32 = order.id.parse().unwrap();
    let s = i.to_string();
    assert_eq!(s, order.id);
}

prop_compose! {
    fn arb_order_id()(id in any::<u32>()) -> String {
        id.to_string()
    }
}
prop_compose! {
    fn arb_order(max_quantity: u32)
                (id in arb_order_id(), item in "[a-z]*",
                 quantity in 1..max_quantity)
                -> Order {
        Order { id, item, quantity }
    }
}

proptest! {
    /*
    #[test]
    */
    fn test_do_stuff(order in arb_order(1000)) {
        do_stuff(order);
    }
}
fn main() { test_do_stuff(); }

We had to extract arb_order_id() out into its own function, but otherwise this desugars to almost exactly what we wrote in the previous section. The generated function takes the first parameter list as arguments. These arguments are used to select the strategies in the second argument list. Values are then drawn from those strategies and transformed by the function body. The actual function has a return type of impl Strategy<Value = T> where T is the declared return type.