Rust mockallでモックインスタンスを導入する

個人開発したアプリの宣伝
目的地が設定できる手帳のような使い心地のTODOアプリを公開しています。
Todo with Location

Todo with Location

  • Yoshiko Ichikawa
  • Productivity
  • Free

スポンサードリンク

RustでのUnit test時にモックインスタンスを導入したので記録しておきます。


mockallの導入

Cargo.toml

[dependencies]
mockall = "0.8.3"


mock化するインスタンスの作成

自分の場合はDBアクセスを行うRepositoryインスタンスをモック化した。実際にDBアクセスするインスタンス定義は以下のようなイメージ。

modelとDBConnectionは自身の環境なんかに読み替えて下さい。モック化するのでTraitを実装します。

account_repository.rs

use diesel::{insert_into, result::Error};
use diesel::prelude::*;
use diesel::result::QueryResult;
use crate::schema::accounts::dsl;
use crate::models::account::Account;
use crate::repositories::connection::DBConnection;

pub struct AccountRepository<'a> {
  pub db: &'a DBConnection<'a>
}

use mockall::*;
#[automock]
pub trait AccountRepositoryTrait {
  fn save(&self, account: &Account) -> QueryResult<String>;
}

impl <'a>AccountRepositoryTrait for AccountRepository<'a>{
  fn save(&self, account: &Account) -> QueryResult<String> {
    insert_into(dsl::accounts)
    .values(account)
    .returning(dsl::id)
    .get_result(self.db.get())
  }
}

Traitに#[automock]マクロを定義することで必要メソッドが定義されたモックインスタンスを作成することができます。


利用例

例えば、サインアップの処理時にAccountRepository.save()を使用するけど、テストではAccountRepositoryを入れ替えたい。とかのケース。

signup_service.rs

#[cfg(test)]
mod tests {
  use super::*;
  use crate::repositories::account_repository::*;

  #[test]
  fn test_signup() {
    //モックの作成とセットアップ
    let mut mock_account_repository = MockAccountRepositoryTrait::new();
    mock_account_repository.expect_save()
    .times(1)
    .returning(|_| Ok("success save accoumt".to_string()));

    let service = SignupApplicationService::new(&mock_account_repository);
    let ans = service.signup();
    assert_eq!(ans, "success save accoumt".to_string());
  }
}

expect_save().time(1)でsuffixのメソッドが何回コールされたかを計測する。コールされた回数が違う場合はpanicする。

returningでモックメソッドの結果を指定できる。ちなみにErrorを返す場合は、

use diesel::result::Error;
mock_account_repository.expect_save()
    .times(1)
    .returning(|_| Err( Error::NotFound ));

こんな感じで定義できる。