mockall icon indicating copy to clipboard operation
mockall copied to clipboard

Feature Request: Add .return_with_context<T>(...) for Stateful Return Logic in Expectations

Open BernardIgiri opened this issue 6 months ago • 0 comments

Summary

Introduce a new mockall API method: .return_with_context<T>(...), allowing you to define stateful behavior per invocation by passing a mutable context value into each call. This simplifies mocking methods that behave differently over time.

Motivation

In stateful black box testing—such as UI loops, dialogue engines, or retry logic—mocked methods are often called repeatedly, producing different results based on internal state or history. Current patterns using RefCell<VecDeque<T>> are verbose and obscure intent.

Proposed API

impl<T> Expectation<T> {
    pub fn return_with_context<C, F>(&mut self, ctx: C, f: F)
    where
        C: 'static,
        F: FnMut(&mut C, Args...) -> Ret + 'static;
}
  • ctx is stored internally and mutated with f on each call.
  • Provides a clean, localized way to manage state within the mock.

🧪 Example 1: Using usize as a Call Counter

#[automock]
trait Greeter {
    fn greet(&self) -> String;
}

let mut mock = MockGreeter::new();

mock.expect_greet()
    .return_with_context(0_usize, |count, _| {
        *count += 1;
        format!("Hello for the {count} time!")
    });

assert_eq!(mock.greet(), "Hello for the 1 time!");
assert_eq!(mock.greet(), "Hello for the 2 time!");

🧪 Example 2: Using String as Editing Context

#[automock]
trait Editor {
    fn edit(&mut self, cmd: &str) -> String;
}

let mut mock = MockEditor::new();

mock.expect_edit()
    .return_with_context(String::new(), |text, cmd| {
        match *cmd {
            "add hello" => text.push_str("hello"),
            "add world" => text.push_str(" world"),
            "delete" => text.clear(),
            _ => (),
        }
        text.clone()
    });

assert_eq!(mock.edit("add hello"), "hello");
assert_eq!(mock.edit("add world"), "hello world");
assert_eq!(mock.edit("delete"), "");

Bonus: Possible Sugar Methods

  • .return_count(...): convenient form of return_with_context(0_usize, ...) where the count is incremented on each call.
  • .return_sequence(...): sugar for working with an iterator, where the next value from the iterator is presented on each call.

Why This Matters

  • Reduces boilerplate and internal mutability
  • Clarifies intent in complex test flows
  • Easily adaptable for broader use cases: FSMs, loops, retry logic, interactive systems

BernardIgiri avatar Jul 16 '25 00:07 BernardIgiri