How to extend ============= ## 1. Write your own Assertion trait. - S: Subject type (e.g. HashSet) - R: Return type of assertion - For `assert_that!` macro, `R` is `()` (or panic when the assertion fails). - For `check_that!` macro, `R` is `CheckThatResult`. ```rust trait SetAssertion { fn contains>(&self, expected: B) -> R; fn is_equal_to>(&self, expected: S) -> R; } ``` ## 2. Write implementation for `Subject`. ```rust impl SetAssertion, T, R> for Subject, (), R> { fn contains>(&self, expected: B) -> R { if (self.actual().contains(expected)) { self.new_result().do_ok() } else { self.new_result() .add_fact("expected to contain", "XXX") .add_fact(...) .do_fail() } } } ``` ## 3. Write tests. ```rust #[cfg(test)] mod tests { use std::iter::FromIterator; use crate::*; use super::*; #[test] fn contains() { assert_that!(HashSet::from_iter(vec![1, 2, 3].iter())).contains(&3); // Failures let result = check_that!(HashSet::from_iter(vec![1, 2, 3].iter())).contains(&10); assert_that!(result).facts_are_at_least(vec![ Fact::new("expected to contain", "10"), Fact::new_simple_fact("but did not"), ]); assert_that!(result) .fact_keys() .contains(&"though it did contain".to_string()); // Skip test for value because key order is not stable. } } ``` Do and Dont =========== DONT: Method chain of multiple independent assertions ----------------------------------------------------- ```rust assert_that!(3).is_less_than(10).is_larger_than(1); assert_that!(vec![1,2,3]).contains_exactly(3, 2, 1).in_order(); // Java Truth like ``` ### DO: ##### Recommended: Independent assertions. ```rust assert_that!(3).is_less_than(10); assert_that!(3).is_larger_than(1); ``` ##### Ok: Define one assertion method to check both. ```rust assert_that!(3).is_in(1, 10); assert_that!(vec![1,2,3]).contains_exactly_in_order(3, 2, 1); ``` ### Why? - Readability - One `assert_that!()` should check one assertion for readability. - Design complication - Return type of `assert_that!(3).is_less_than(10)` will be complicated like `Result, R>`. DO: Derived subjects -------------------- ```rust assert_that!(hash_map).key_set().contains( & "foo"); assert_that!(hash_map).key_set().contains_exactly( & "foo", & "bar"); ``` It is one option not to use derived subject when the assertion method is expected to be used frequently and when the api of derived subject is hard to use (`&` in argument in previous example). ##### Ok ```rust assert_that!(hash_map).contains_key("foo"); assert_that!(hash_map).contains_key("bar"); ``` ### Why? - API simplicity - To avoid having many method having `key_set` prefix ( ex. `assert_that!(..).key_set_contains(), assert_that!(..).key_set_contains_exactly()`) - Code duplication - Derived subject enables to reuse the existing implementation easily. ### What is `derived subject`? A feature to create a new subject whose type is different from the original subject type. - `Subject.new_subject(..)` - `Subject.new_owned_subject(..)` ```rust impl<'a, K, V, R> MapAssertion<'a, K, V, R> for Subject<'a, HashMap, (), R> where AssertionResult: ReturnStrategy, { fn key_set(&self) -> Subject<'a, Keys, (), R> { // <- Returns new subject whose type is Keys<> from HashMap<> self.new_owned_subject( self.actual().keys(), Some(format!("{}.keys()", self.description_or_expr())), (), ) } } ```