Avoid making parameters mutable due to testing requirements

I've started extracting functions to traits in order to provide a different implementation during testing. For example:

pub trait Broadcast {
    fn broadcast_message(&self, message: &str);
}
pub struct PrintLnBroadcast {}
impl Broadcast for PrintLnBroadcast {
    fn broadcast_message(&self, message: &str) {
        println!("{}", message);
    }
}

This works fine, but when I want to add a test implementation of Broadcast, it often requires making the self parameter mutable.

Continuing with the example:

    struct AccumulatingBroadcast {
        messages: Vec<Box<str>>
    }
    impl Broadcast for AccumulatingBroadcast {
        fn broadcast_message(&mut self, message: &str) {
            self.messages.push(Box::from(message));
        }
    }

The AccumulatingBroadcast struct's contents can then be inspected to verify the right broadcasts were called.

With this approach, the mutability of the self parameter becomes infectious and requires changing the function in the trait too, and thus also in the production implementation.

Is there a better way to do this than having this explosion of mutability? I don't want test requirements to decrease the quality of production code.

I'm aware of crates like mockall, but I'm wondering if there's a way to do this in standard Rust for learning purposes.

You can use a RefCell.

struct AccumulatingBroadcast {
    messages: RefCell<Vec<Box<str>>>,
}
impl Broadcast for AccumulatingBroadcast {
    fn broadcast_message(&self, message: &str) {
        self.messages.borrow_mut()
            .push(Box::from(message));
    }
}
2 Likes

Remember that &/&mut is strictly about shared/unique, and the mutability aspect is just the most common manifestation of that effect. So, as alice mentions, you can use interior mutability behind a & (like mutable in C++, if you're familiar with that).

Or an Arc<Mutex> if you use threads. Interior mutability is perfect for this task.

Got it, thanks all. I tried RefCell and it works great for the task.

You're right, I should have worded things better. I will learn to better phrase these terms with time.

It wasn't a critique of your wording.

"mut[able]" is even in the syntax for &mut, so saying "mutable reference" is completely normal.

It's just a reminder that if you're hitting a more complicated scenario, using the different perspective on them can help find which one you really need. (This is also commonly the case for things like caches or pools.)

1 Like