You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Mockall中FnMut参数以&而非&mut传递无法调用的问题求助

Mock FnMut 参数时的编译错误

问题场景

定义了包含for_each方法的trait Foo,使用mockall的automock和concretize宏生成Mock,测试中尝试Mockfor_each方法并调用传入的闭包时,出现编译错误:Mock生成的参数是不可变引用的FnMut trait对象,无法调用(因为FnMut需要可变引用)。

定义的Trait与测试目标代码

#[cfg_attr(test, automock)]
trait Foo {
    #[cfg_attr(test, concretize)]
    fn for_each<F>(&self, processor: F)
    where
        Self: Sized,
        F: FnMut(&u32);
}

#[derive(Default)]
struct UnderTest;

impl UnderTest {
   fn do_something<F: Foo>(&self, foo: &F) {
     foo.for_each(|x| {
         eprintln!("Invoked with {x}");
     })
   }
}

测试代码

#[cfg(test)]
fn do_something() {
   let mut foo = MockFoo::new();
   foo.expect_for_each().times(1).returning(|cb| {
     cb(&0);
     cb(&1);
   });

   let t = UnderTest::default();
   t.do_something(&foo);
}

编译错误信息

expected closure signature `for<'a> fn(&'a (dyn for<'a> std::ops::FnMut(&'a u32))) -> _`
   found closure signature `for<'a> fn(&'a mut (dyn for<'a> std::ops::FnMut(&'a u32))) -> _`

原因分析

问题出在mockall的concretize宏处理逻辑:for_each接收的是F: FnMut(&u32)类型的参数,concretize会将这个泛型参数抽象为trait对象,但默认包装成了不可变引用&dyn FnMut(...)。而FnMut闭包的调用会修改其内部状态,必须通过可变引用才能调用,因此直接调用不可变引用的闭包会触发编译错误。


解决方案

方案1:修改Trait方法的参数传递方式(推荐)

调整trait的for_each方法,让闭包以可变引用的形式传递,这样mockall生成的Mock参数类型会匹配FnMut的调用要求:

#[cfg_attr(test, automock)]
trait Foo {
    #[cfg_attr(test, concretize)]
    fn for_each<F>(&self, processor: &mut F)
    where
        Self: Sized,
        F: FnMut(&u32);
}

同时修改UnderTest中的调用代码:

impl UnderTest {
   fn do_something<F: Foo>(&self, foo: &F) {
     let mut cb = |x| {
         eprintln!("Invoked with {x}");
     };
     foo.for_each(&mut cb);
   }
}

测试代码无需修改,此时returning闭包接收的是&mut dyn FnMut(...),可以正常调用闭包。

方案2:移除concretize宏,显式指定泛型参数

如果不想修改原trait的签名,可以去掉#[cfg_attr(test, concretize)],然后在测试中显式指定for_each的泛型参数类型:

// 修改后的Trait定义
#[cfg_attr(test, automock)]
trait Foo {
    fn for_each<F>(&self, processor: F)
    where
        Self: Sized,
        F: FnMut(&u32);
}

测试代码调整为:

#[cfg(test)]
fn do_something() {
   let mut foo = MockFoo::new();
   // 显式指定泛型参数为Box包裹的FnMut trait对象
   type TestCallback = Box<dyn FnMut(&u32)>;
   foo.expect_for_each::<TestCallback>().times(1).returning(|mut cb| {
     cb(&0);
     cb(&1);
   });

   let t = UnderTest::default();
   t.do_something(&foo);
}

这种方式避免了修改原代码,但需要显式指定泛型参数,灵活性稍差。


内容的提问来源于stack exchange,提问作者Vitali

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.24 06:34:54