if you're working in Rust, I recommend that you learn how to work with macro_rules. specifically, I recommend this because you can use macros to build custom assertions that can remove a lot of boilerplate from your test functions.

for example, if you have an iterator that produces some complicated enum or struct, you could write a macro that will:

in fact, I can show you that very scenario.

that very scenario

as I've mentioned elsewhere, I'm working on a project called yeet-note. one of the essential components of yeet-note is being able to compare the filesystem tree under a path with an index of that tree.

I wrote a couple of integration tests that cover simple cases of the indexing scan. the indexer produces an iterator containing the results, and in the test scenarios, i expect specific events.

to implement this, I wrote this macro for the test module

macro_rules! expect_next_success {
    (@next $i:ident,
      $position:expr) => {{
        let next = match $i.next() {
            None => bail!(
                "missing an entry @\"{}\"",
                $position
            ),
            Some(v) => v,
        };
        let success = next.context(format_err!(
            "expected next entry to be a success @\"{}\"",
            $position
        ))?;
        success
    }};

    (@check_var $i: ident,
      $variant: ident,
      $position: expr,
      $msg: expr) => {{
        let next_here = expect_next_success!(
            @next $i,
            $position
        );
        let res = match next_here {
            FileIndexEvent::$variant(capture) => capture,
            o => bail!(
                "expected event @\"{}\" \
                to be {}: {}. instead got {:?}",
                $position, stringify!($variant), $msg, o
            ),
        };
        res
    }};

    (created $i: ident,
      $position:expr,
      $msg: expr) => {
        expect_next_success!(
            @check_var $i,
            Created,
            $position,
            $msg
        )
    };

    (updated $i: ident,
      $position:expr,
      $msg: expr) => {
        expect_next_success!(
            @check_var $i,
            Updated,
            $position,
            $msg
        )
    };
    (unchanged $i: ident,
      $position:expr,
      $msg: expr) => {
        expect_next_success!(
            @check_var $i,
            Unchanged,
            $position,
            $msg
        )
    };
    (deleted $i: ident,
      $position:expr,
      $msg: expr) => {
        expect_next_success!(
            @check_var $i,
            Deleted,
            $position,
            $msg
        )
    };
}

taking advantage of internal rules and the way that tokens are matched, it allows me to specify the events that I expect to see during each scan fairly easily:

let mut res_iter = ops.do_scan(2)?.into_iter();

let res: IndexedFile<i64> = expect_next_success!(
    created res_iter,
    "first scan first entry",
    "first time the entry has been seen"
);

assert_eq!(res.entry().name(), "test");
assert_eq!(
    *res.entry().entry_type(),
    fs::EntryType::Directory)
;

and follow up assertion with the next one i expect easily.

let res: IndexedFile<i64> = expect_next_success!(
    created res_iter,
    "first scan second entry",
    "first time the entry has been seen"
);

assert_eq!(res.entry().name(), "hello.txt");
assert_eq!(
    *res.entry().entry_type(),
    fs::EntryType::File
);

expect_none_next!(res_iter);

(expect_none_next is a much simpler macro; see the earlier link)

since I have to do this kind of expectation many times during these tests, having the whole structure factored out makes life easy:

let mut res_iter = ops.do_scan(2)?.into_iter();

let res: IndexedFile<i64> = expect_next_success!(
    unchanged res_iter,
    "second scan first entry",
    "directory had no changes"
);

assert_eq!(res.entry().name(), "test");
assert_eq!(
    *res.entry().entry_type(),
    fs::EntryType::Directory
);

let res: IndexedFile<i64> = expect_next_success!(
    unchanged res_iter,
    "second scan second entry",
    "file had no changes"
);

assert_eq!(res.entry().name(), "hello.txt");
assert_eq!(
    *res.entry().entry_type(),
    fs::EntryType::File
);

expect_none_next!(res_iter);

why not just use a function?

in my specific example, there are several problems you'd run into trying to do it with a function:

learn more

macro_rules based macros aren't as well documented as other parts of the language, and there's a lot about them that I found pretty hard to understand.

however, I found this reference guide, The Little Book of Rust Macros, to be helpful in learning both the basics of macro syntax as well as some fairly sophisticated patterns.