jens_vdh
jens_vdh

Reputation: 137

How can I test if a callback closure has been called?

Consider my struct WareHouse which has a watch function which takes a closure as an argument.

The struct also has an update function which updates a path and a value, and should then call the closure passed into the watch function if applicable.

How can I verify that when I call watch(callback) followed by an update the callback function is triggered?

I tried:

#[test]
fn calls_watcher_on_update() {
    let mut w = WareHouse::new();
    let mut called = false;
    // set a watcher 
    w.watch(move|| {
        println!("watcher called");
        called = true;
    });
    w.update("/foo", "baz").unwrap();
    assert!(called);
}

Upvotes: 5

Views: 349

Answers (2)

Jeremy Meadows
Jeremy Meadows

Reputation: 2581

A different way would be to encapsulate the called status into your Warehouse struct. If you need that value to follow your struct around as it's passed to functions or something, this will handle that without needing to track multiple variables:

struct Warehouse {
    callback: Option<fn() -> ()>,
    called: bool,
}

impl Warehouse {
    fn new() -> Self {
        Self {
            callback: None,
            called: false,
        }
    }

    fn watch(&mut self, f: fn() -> ()) {
        self.callback = Some(f);
    }

    fn update(&mut self, path: &str, value: &str) -> Result<(), ()> {
        if let Some(cb) = self.callback {
            cb();
            self.called = true;
            Ok(())
        } else {
            Err(())
        }
    }

    fn was_called(&self) -> bool {
        self.called
    }
}

fn main() {
    let mut w = Warehouse::new();
    // set a watcher
    w.watch(move|| {
        println!("watcher called");
    });

    // updates *and* calls the closure
    w.update("/foo", "baz").unwrap();
    
    // getter method to see if it was called (could also be pub member)
    assert!(w.was_called());
}

Upvotes: 2

cdhowie
cdhowie

Reputation: 169403

When you try to move a value that implements Copy, it is copied instead. So your closure captures the value of called the moment that it was created; called within and outside of the closure are effectively two different variables.

You should even be getting a warning on this, something like:

warning: value captured by `called` is never read

Instead, consider capturing a Cell<bool> by reference. I'm inferring that you need a Cell here because capturing by mutable reference would require that w drop the closure before you can read the value in assert!() or you would get a "cannot borrow mutably while already borrowed" error.

use std::cell::Cell;

fn main() {
    // Doesn't work right; called is copied into the closure, not moved.
    {
        let mut called = false;
        let mut closure = move || {
            called = true;
            println!("(A) In closure, called is {called}");
        };
        closure();
        println!("(A) Outside of closure, called is {called}");
    }
    
    // Workes correctly; the closure captures a reference to the cell.
    {
        let called = Cell::new(false);
        let closure = || {
            called.set(true);
            println!("(B) In closure, called is {}", called.get());
        };
        closure();
        println!("(B) Outside of closure, called is {}", called.get());
    }
}

This will output:

(A) In closure, called is true
(A) Outside of closure, called is false
(B) In closure, called is true
(B) Outside of closure, called is true

(Playground)

Upvotes: 4

Related Questions