我试图模拟serialport::new()
提供的串口对象,并花了很长时间。Rc<RefCell<dyn SomeTrait>>
模式是我一直在使用的一种模式,现在用于任何单例依赖项-比如串口-我已经习惯了它。它可以工作,它让我使用mockall
编写高度集中的单元测试,其中所有依赖项都被模拟出来。
但是,今天我遇到了serialport::new()
,它返回一个Box<dyn SerialPort>
,我想不出一种方法把它转换成Rc<RefCell<dyn SerialPort>>
。所以我 Package 了整个盒子,最后得到了Rc<RefCell<Box<dyn SerialPort>>>
。但是现在如果不把TestContext
中的mock对象定义为另一个Rc<RefCell<Box<dyn SerialPort>>>
,我就无法编译我的单元测试。这将阻止我实际调用特殊的mock相关方法,如.expect_send()
。
这里我需要一些帮助。下面是以最简单的形式重新创建的用例,以及在构建测试时遇到的编译器错误。
use std::cell::RefCell;
use std::rc::Rc;
/// This is not my code. This is a simplified version of the 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>>>`
我还将代码发布到GitHub repo,如果你想在本地复制它:https://github.com/DavidZemon/MockingProblems/
1条答案
按热度按时间niknxzdl1#
我们可以完全忽略
RefCell
,并考虑当T
实现Trait
时将Rc<Box<T>>
转换为Rc<Box<dyn Trait>>
的问题。为了做到这一点,这必须不改变
Rc
内部值的内存表示,因此Box<T>
和Box<dyn Trait>
必须具有相同的内存表示。然而,Box<T>
是一个瘦指针,这意味着它只包含堆上值的地址,而Box<dyn Trait>
是一个胖指针。它包含值的地址和T
的vtable的地址,T
包含运行时的类型信息(如方法)。所以
Box<dyn Trait>
大于Box<T>
,因此具有不同的存储器布局,所以不可能把Rc<Box<T>>
变成Rc<Box<dyn Trait>>
(或者以任何方式产生两个Rc
,这些类型指向相同的值)。(或任何其他指针类型)是使这不可能的原因。