asomers / mockall

A powerful mock object library for Rust
Apache License 2.0
1.5k stars 62 forks source link

Feature request: support for `const generics` in `mock!` and `automock` #568

Open korbinian-maier-bl opened 6 months ago

korbinian-maier-bl commented 6 months ago

Hi, first of all, we this crate extensively, it makes testing and mocking easy and accessible. Thanks for that!

We now hit one limitation though: const generics are not yet supported as the macro tells us: message: #automock does not yet support generic constants.

For instance, if you have a struct with an array

struct Status<const N: usize> {
    elements: [u32; N],
}

neither automock nor mock! for a Status<const N: usize> works as expected.

Is the support of const generics planned or are there any major roadblocks preventing the implementation?

If it's just a matter of resources I'd like to try and implement this even (if you'd be willing to give some guidance of course).

asomers commented 6 months ago

More fundamentally, mocking fields isn't even possible. How would you propose accessing the mock object's elements array?

korbinian-maier-bl commented 6 months ago

Yeah, sorry. That example doesn't make sense. While trying to come up with a simple thing I got confused.

Next try: the trait FixedContainer can be mocked but the Container trait can't be.

// #[cfg_attr(test, mockall::automock)] // Macro panics
pub trait Container<const N: usize> {
    fn times(&mut self, m: [u32; N]);
    fn add(&mut self, m: [u32; N]);
}

#[cfg_attr(test, mockall::automock)]
pub trait FixedContainer {
    fn times(&mut self, m: [u32; 3]);
    fn add(&mut self, m: [u32; 3]);
}

pub struct Outer<T: FixedContainer> {
    container: T,
}

impl<T: FixedContainer> Outer<T> {
    pub fn new(container: T) -> Self {
        Self { container }
    }

    pub fn arithmetic(&mut self, number: u32, add: bool) {
        if add {
            self.container.add([number; 3]);
        } else {
            self.container.times([number; 3]);
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn fixed_container() {
        let mut mocked = MockFixedContainer::new();
        mocked.expect_add().once().returning(|_| ());
        mocked.expect_times().once().returning(|_| ());

        let mut outer = Outer::new(mocked);
        outer.arithmetic(1, true);
        outer.arithmetic(1, false);
    }
}

Does that make more sense? Is there any fundamental reason why this can't be supported?

asomers commented 6 months ago

I think it could be done. But just as you must for associated types. you'd have to tell automock what value of N to use.

korbinian-maier-bl commented 6 months ago

So something like #[automock(const N=5;)] or whatever keyword you'd choose for the const generic, right?

astapleton commented 2 months ago

+1 on this. I love using automock, but I frequently run up against the limitation on not being able to use const generics in trait definitions

asomers commented 2 months ago

BTW, while Mockall doesn't currently support const generics, it does support associated constants. So you might be able to refactor your code to use those, instead. See https://github.com/asomers/mockall/blob/master/mockall/tests/automock_associated_const.rs for an example.