Skip to content

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

@BernardIgiri

Description

@BernardIgiri

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions