[Rust] mockall_doubleで構造体をモックをする

mockall_double crateで構造体のモックを手軽に実現できます。しかしモック対象の構造体のコンストラクタ呼び出しがテストプロファイルだとコンパイルできなくなる点に注意が必要です。
2021.04.26

はじめに

Rustでテストを書くとき、構造体のモックをしたかったのでmockall_double crate を試してみました。

どうやって構造体をモックするのか

mockall_doubleではマクロによって以下のような構造体のコードを生成します。

生成されるモック用の構造体にはセットアップ用のメソッドが定義されていて、テストコードでは元の構造体の代わりに使用できます。cfg アトリビュートでテスト時と実行時にモックと元の実装を切り替えしています。

元のコード

mod shape {
	pub struct Rectangle {
	    pub width: u32,
	    pub height: u32,
	}
}

use mockall_double::double;
#[double]
use shape::Rectangle;

生成されるコード

mod shape {
	pub struct Rectangle {
	    pub width: u32,
	    pub height: u32,
	}
	#[cfg(test)]
	pub struct MockRecangle {}
}

#[cfg(not(test))]
use shape::Rectangle;
#[cfg(test)]
use shape::MockRectangle as Rectangle;

使用例

上記を踏まえてた使用例は以下の通りです。

//モックしたい構造体
mod shapes {
    //testプロファイルの時のみマクロをuse
    #[cfg(test)]
    use mockall::automock;

    pub struct Rectangle {
        pub width: u32,
        pub height: u32,
    }

    #[cfg_attr(test, automock)] //testプロファイルの時のみautomockを適用する
    impl Rectangle {
        pub fn area(&self) -> u32 {
            self.width * self.height
        }
    }
}

use mockall_double::double;

#[double]
use shapes::Rectangle;

//print_areaはtestでは定義されていないのでアトリビュートをつけておく
//あるいはtest用にダミーの実装を作っておく
#[cfg(not(test))]
fn main() {
    print_area();
}

//テスト以外ではRectangleはモックになって定義が一致せずコンパイルできないので
//アトリビューションでテスト以外の場合のみ、としておく
#[cfg(not(test))]
fn print_area() {
    let rect = Rectangle {
        width: 5,
        height: 3,
    };
    println!("area * 2 =  {}", double_rectangle_area(&rect));
}

// この関数をテストしたい
fn double_rectangle_area(r: &Rectangle) -> u32 {
    r.area() * 2
}

#[cfg(test)]
mod test {
    use super::double_rectangle_area;
    #[double]
    use crate::shapes::Rectangle;
    use mockall_double::double;

    #[test]
    fn test_double_reactangle_area() {
        //Rectangleのモック
        let mut rect = Rectangle::default();
        //1を返す
        rect.expect_area().times(1).returning(|| 1);
        
        assert_eq!(double_rectangle_area(&rect), 2);
    }
}

構造体の生成コードの問題

print_area 関数ではRectangleを生成していてtestプロファイルではコンパイルできないので、アトリビュートによって、エラーになるのを回避しています。これはモック対象の構造体を扱う箇所が増えてくると厄介な問題です。プロファイルに応じて切り替えをする方法上避けるのが難しいのですが、以下のような回避方法があると思います。

  • 構造体の生成をnew メソッドに集約し、テストプロファイルに同じシグネチャの実装を加える
  • (例にあったように)アトリビュートをつけて回避する

まとめ

mockall_double crateで構造体のモックを手軽に実現できます。しかしモック対象の構造体のコンストラクタ呼び出しがテストプロファイルだとコンパイルできなくなる点に注意が必要です。