I'm strictly trying to mock the std::path::PathBuf.is_dir method, but I think there is a more generic use case here where really this is about mocking an external feature.
I've created a trait that encapsulates the PathBuf.is_dir method which theoretically, according to the mockall documentation should enable me to mock my is_dir encapsulation.
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();
}
}
All these tests fail, as follows. It looks to me like the mocks that are available (the tests are finding the various mock contexts) are not being consumed by the running test.
---- 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