我严格地尝试模拟std::path::PathBuf.is_dir
方法,但是我认为这里有一个更通用的用例,其中实际上是关于外部特征的模拟。
我创建了一个特征,其中封装了PathBuf.is_dir
方法,根据mockall documentation,理论上,该方法应该可以模拟is_dir
封装。
use mockall::*;
use std::path::PathBuf;
#[derive(Debug, Clone, PartialEq)]
pub enum PackageFileIndexError {
ArchiveRootNotADirectory,
}
#[automock]
trait PathInterface {
// Encapsulate the is_dir method to make it mockable.
fn is_dir(this_path: &PathBuf) -> bool {
this_path.is_dir()
}
}
pub struct PackageFileIndexData {
archive_root_path: PathBuf,
}
impl PackageFileIndexData {
pub fn new(archive_root: &str) -> Result<PackageFileIndexData, PackageFileIndexError> {
let archive_root_path = PathBuf::from(archive_root.clone());
if !Self::is_dir(&archive_root_path) {
return Err(PackageFileIndexError::ArchiveRootNotADirectory);
}
Ok(PackageFileIndexData { archive_root_path })
}
}
impl PathInterface for PackageFileIndexData {}
#[cfg(test)]
mod tests {
use super::*;
mock! {
PackageFileIndexData {}
trait PathInterface {
fn is_dir(this_path: &PathBuf) -> bool;
}
}
#[test]
fn test_bad_directory() {
let ctx = MockPackageFileIndexData::is_dir_context();
ctx.expect().times(1).returning(|_x| false);
let result = PackageFileIndexData::new("bad_directory").err().unwrap();
assert_eq!(result, PackageFileIndexError::ArchiveRootNotADirectory);
}
#[test]
fn test_good_directory() {
let ctx = MockPackageFileIndexData::is_dir_context();
ctx.expect().times(1).returning(|_x| true);
let _result = PackageFileIndexData::new("good_directory").unwrap();
}
#[test]
fn test_bad_directory2() {
let ctx = MockPathInterface::is_dir_context();
ctx.expect().times(1).returning(|_x| false);
let result = PackageFileIndexData::new("bad_directory").err().unwrap();
assert_eq!(result, PackageFileIndexError::ArchiveRootNotADirectory);
}
#[test]
fn test_good_directory2() {
let ctx = MockPathInterface::is_dir_context();
ctx.expect().times(1).returning(|_x| true);
let _result = PackageFileIndexData::new("good_directory").unwrap();
}
}
所有这些测试均失败,如下所示。在我看来,正在运行的测试并没有消耗可用的模拟(测试正在查找各种模拟上下文)。
---- mock_is_dir::tests::test_good_directory1 stdout ----
thread 'mock_is_dir::tests::test_good_directory1' panicked at 'called `Result::unwrap()` on an `Err` value: ArchiveRootNotADirectory', src/mock_is_dir.rs:63:23
---- mock_is_dir::tests::test_bad_directory2 stdout ----
thread 'mock_is_dir::tests::test_bad_directory2' panicked at 'MockPathInterface::is_dir: Expectation(<anything>) called fewer than 1 times', src/mock_is_dir.rs:10:1
---- mock_is_dir::tests::test_good_directory2 stdout ----
thread 'mock_is_dir::tests::test_good_directory2' panicked at 'called `Result::unwrap()` on an `Err` value: ArchiveRootNotADirectory', src/mock_is_dir.rs:81:23
---- mock_is_dir::tests::test_bad_directory1 stdout ----
thread 'mock_is_dir::tests::test_bad_directory1' panicked at 'MockPackageFileIndexData::is_dir: Expectation(<anything>) called fewer than 1 times', src/mock_is_dir.rs:40:5
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
failures:
mock_is_dir::tests::test_bad_directory1
mock_is_dir::tests::test_bad_directory2
mock_is_dir::tests::test_good_directory1
mock_is_dir::tests::test_good_directory2
test result: FAILED. 0 passed; 4 failed; 0 ignored; 0 measured; 0 filtered out
答案 0 :(得分:2)
不幸的是,至少在您想要的方式下,您正在做的事情行不通。 #[automock]
正在创建一个名为struct
的{{1}}。然后,您可以将该类型传递到期望实现MockPathInterface
的函数中。它至少不能直接改变实现该特征的现有结构的行为。您确实在测试中正确设置了PathInterface
,但是没有将其与MockPathInterface
关联,因此它永远不会被使用。
执行此操作的一种方法是我修改代码以传递行为,然后可以传递模拟行为。例如:
PackageFileInfoData
请注意,我们在#[automock]
pub trait PathInterface {
// Encapsulate the is_dir method to make it mockable.
fn is_dir(this_path: &PathBuf) -> bool {
this_path.is_dir()
}
}
pub struct PackageFileIndexData {
archive_root_path: PathBuf,
}
impl PackageFileIndexData {
pub fn new<PI: PathInterface>(archive_root: &str) -> Result<PackageFileIndexData, PackageFileIndexError> {
let archive_root_path = PathBuf::from(archive_root.clone());
if !PI::is_dir(&archive_root_path) {
return Err(PackageFileIndexError::ArchiveRootNotADirectory);
}
Ok(PackageFileIndexData { archive_root_path })
}
}
静态方法中使用了通用参数来传入实现特征的类型。还要注意,我们将new
调用更改为引用该泛型类型。
然后,您可以修改测试以使用该类型参数(以下是一个示例):
is_dir
这里唯一的变化是使用turbfish(#[test]
fn test_bad_directory() {
let ctx = MockPathInterface::is_dir_context();
ctx.expect().times(1).returning(|_x| false);
let result = PackageFileIndexData::new::<MockPathInterface>("bad_directory").err().unwrap();
assert_eq!(result, PackageFileIndexError::ArchiveRootNotADirectory);
}
)运算符将类型传递给::<>
静态方法。
这些更改在美学上很难看,我并不一定建议您在所有地方都使用此模式-如果您确实必须嘲笑很多这样的行为,则基本上是无法维护的。但这说明了如何实现,以及Rust中模拟的局限性。
答案 1 :(得分:0)
使用cfg-if
正是模拟具体结构时要使用Mockall的方式。