解决MockSerialPort与Rc<RefCell<Box<dyn SerialPort>>>类型不匹配问题
解决SerialPort Mock时的类型不匹配问题
问题描述
尝试mock serialport::new()返回的串口对象时遇到类型不匹配问题。习惯用Rc<RefCell<dyn SomeTrait>>模式处理单例依赖并配合mockall编写单元测试,但serialport::new()返回Box<dyn SerialPort>,只能包装为Rc<RefCell<Box<dyn SerialPort>>>。测试时:
- 若将mock对象定义为
Rc<RefCell<Box<dyn SerialPort>>>,无法调用mock专属方法 - 若定义为
Rc<RefCell<Box<MockSerialPort>>>,则出现类型不匹配错误
简化代码
use std::cell::RefCell; use std::rc::Rc; /// 简化版serialport crate mod serialport { pub trait SerialPort { fn send(&self); } pub struct SomeConcreteSerialPort {} impl SerialPort for SomeConcreteSerialPort { fn send(&self) {} } pub fn new() -> Box<dyn SerialPort> { Box::new(SomeConcreteSerialPort {}) } } struct MyStruct { port: Rc<RefCell<Box<dyn serialport::SerialPort>>>, } impl MyStruct { fn new() -> Self { Self { port: Rc::new(RefCell::new(serialport::new())), } } fn do_send(&self) { self.port.borrow().send(); } } fn main() { let my_struct = MyStruct::new(); my_struct.do_send(); println!("The foo is done!"); } #[cfg(test)] mod test { use mockall::mock; use std::cell::RefCell; use std::rc::Rc; use crate::serialport; use crate::MyStruct; mock! { SerialPort {} impl serialport::SerialPort for SerialPort { fn send(&self); } } struct TestContext { mock_port: Rc<RefCell<Box<MockSerialPort>>>, testable: MyStruct, } impl TestContext { fn new() -> Self { let mock_port = Rc::new(RefCell::new(Box::new(MockSerialPort::new()))); Self { mock_port: Rc::clone(&mock_port), testable: MyStruct { port: mock_port }, } } } #[test] fn test_happy_path() { let context = TestContext::new(); context.mock_port.borrow().expect_send().once(); context.testable.do_send(); } }
编译错误
error[E0308]: mismatched types --> src/main.rs:71:44 | 71 | testable: MyStruct { port: mock_port }, | ^^^^^^^^^ expected trait object `dyn SerialPort`, found struct `MockSerialPort` | = note: expected struct `Rc<RefCell<Box<(dyn SerialPort + 'static)>>>` found struct `Rc<RefCell<Box<MockSerialPort>>>`
解决方案
核心思路是调整MyStruct的依赖结构,移除多余的Box包装,直接使用Rc<RefCell<dyn SerialPort>>存储串口对象,这样既兼容原有的serialport::new()返回值,又能直接注入mock对象并保留调用mock方法的能力。
修改后的代码
use std::cell::RefCell; use std::rc::Rc; /// 简化版serialport crate mod serialport { pub trait SerialPort { fn send(&self); } pub struct SomeConcreteSerialPort {} impl SerialPort for SomeConcreteSerialPort { fn send(&self) {} } pub fn new() -> Box<dyn SerialPort> { Box::new(SomeConcreteSerialPort {}) } } struct MyStruct { // 移除多余的Box包装,直接存储Rc<RefCell<dyn SerialPort>> port: Rc<RefCell<dyn serialport::SerialPort>>, } impl MyStruct { // 新增带参数的构造函数,方便测试注入依赖 fn with_port(port: Rc<RefCell<dyn serialport::SerialPort>>) -> Self { Self { port } } fn new() -> Self { // 将serialport::new()返回的Box<dyn SerialPort>转换为Rc<dyn SerialPort> let rc_port: Rc<dyn serialport::SerialPort> = Rc::from(serialport::new()); Self::with_port(Rc::new(RefCell::new(rc_port))) } fn do_send(&self) { self.port.borrow().send(); } } fn main() { let my_struct = MyStruct::new(); my_struct.do_send(); println!("The foo is done!"); } #[cfg(test)] mod test { use mockall::mock; use std::cell::RefCell; use std::rc::Rc; use crate::serialport; use crate::MyStruct; mock! { SerialPort {} impl serialport::SerialPort for SerialPort { fn send(&self); } } struct TestContext { mock_port: Rc<RefCell<MockSerialPort>>, testable: MyStruct, } impl TestContext { fn new() -> Self { let mock_port = Rc::new(RefCell::new(MockSerialPort::new())); // 将MockSerialPort的Rc<RefCell>转换为Rc<RefCell<dyn SerialPort>> let dyn_port: Rc<RefCell<dyn serialport::SerialPort>> = Rc::clone(&mock_port) as _; Self { mock_port, testable: MyStruct::with_port(dyn_port), } } } #[test] fn test_happy_path() { let context = TestContext::new(); // 调用mock的专属方法 context.mock_port.borrow_mut().expect_send().once(); context.testable.do_send(); } }
关键改动说明
- 调整MyStruct的成员类型:将
port从Rc<RefCell<Box<dyn SerialPort>>>改为Rc<RefCell<dyn SerialPort>>,减少一层不必要的Box包装,利用Rc实现所有权共享。 - 新增依赖注入构造函数:添加
with_port方法,允许测试时注入自定义的串口对象(包括mock)。 - 兼容原构造逻辑:在
new方法中,通过Rc::from()将Box<dyn SerialPort>转换为Rc<dyn SerialPort>,保持原有功能不变。 - 测试中类型转换:将
Rc<RefCell<MockSerialPort>>强制转换为Rc<RefCell<dyn SerialPort>>,既满足MyStruct的类型要求,又保留对mock对象的引用以调用mock方法。
内容的提问来源于stack exchange,提问作者DavidZemon
相关产品推荐
相关产品推荐

