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

解决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();
    }
}

关键改动说明

  1. 调整MyStruct的成员类型:将port从Rc<RefCell<Box<dyn SerialPort>>>改为Rc<RefCell<dyn SerialPort>>,减少一层不必要的Box包装,利用Rc实现所有权共享。
  2. 新增依赖注入构造函数:添加with_port方法,允许测试时注入自定义的串口对象(包括mock)。
  3. 兼容原构造逻辑:在new方法中,通过Rc::from()将Box<dyn SerialPort>转换为Rc<dyn SerialPort>,保持原有功能不变。
  4. 测试中类型转换:将Rc<RefCell<MockSerialPort>>强制转换为Rc<RefCell<dyn SerialPort>>,既满足MyStruct的类型要求,又保留对mock对象的引用以调用mock方法。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.24 14:47:07