Rust высмеивает черту, поглощенную другой структурной реализацией - PullRequest
2 голосов
/ 02 апреля 2020

Я строго пытаюсь смоделировать метод std::path::PathBuf.is_dir, но я думаю, что здесь есть более общий случай использования c, где на самом деле речь идет о насмешке над внешней функцией.

Я создал черта, которая инкапсулирует метод PathBuf.is_dir, который теоретически, согласно документации mockall , должен позволять мне высмеивать мою 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

1 Ответ

2 голосов
/ 03 апреля 2020

К сожалению, то, что вы делаете, не будет работать, по крайней мере, так, как вы хотите. #[automock] создает struct с именем 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 })
    }
}

Обратите внимание, что мы используем параметр generi c в методе new stati c для передачи в Тип реализации черты. Также обратите внимание, что мы изменили вызов is_dir для ссылки на этот тип generi c.

Затем вы можете изменить свои тесты, чтобы использовать этот параметр типа (один из примеров ниже):

#[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);
}

Единственное изменение здесь - это использование оператора turbofi sh (::<>) для передачи типа в метод new stati c.

Эти изменения эстетически безобразны, и Я не обязательно рекомендую использовать этот шаблон повсюду - если вам действительно нужно издеваться над таким поведением, это в основном не поддерживается. Но это показывает, как вы можете это сделать, и ограничения насмешек в Rust.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...