Rustモックテスト完全ガイド|mockall・mockito・fakeの使い分けと実践コード
Rustでテストを書いていると、「外部APIやデータベースに依存する処理をどうテストするか」という壁にぶつかります。他言語なら依存オブジェクトをモックに差し替えて済む場面でも、Rustでは所有権・借用ルールや厳格な型システムが絡むため、同じようにはいきません。 Rustのモックテストには複数のアプローチがあり、traitベースのDI設計を軸にしたmockall、HTTPレベルのモックサーバーを提供するmockitoやwiremock、ダミーデータ生成のfake、スナップショット比較のinstaといったクレートが用途ごとに使い分けられています。 Rustにおけるテストダブルの分類 モック(Mock)はテストダブル(Test Double)の一種です。テストダブルとは、テスト対象が依存するコンポーネントの「代役」を指す総称で、目的に応じて以下のように分かれます。 種類 役割 Rustでの典型的な実現手段 Stub 固定値を返す traitの手動実装 / mockallのreturning() Mock 呼び出し内容を検証する mockallのexpect_*() + times() Fake 簡易的な代替実装 HashMapによるインメモリリポジトリ Spy 実処理を通しつつ記録する RefCell<Vec<T>>で呼び出し履歴を保持 Rustの型システムでは、依存コンポーネントの差し替えにtraitを使うのが一般的です。本番用の構造体とテスト用の構造体が同じtraitを実装し、ジェネリクスまたはトレイトオブジェクト経由で注入します。 traitとジェネリクスによるDI設計 外部ライブラリを使わずにモックテストを実現する基本パターンです。依存をtrait化し、テスト時に別の実装を差し込みます。 trait定義とジェネリクスによる注入 // domain層: リポジトリのインターフェイス pub trait UserRepository { fn find_by_id(&self, id: u64) -> Option<String>; fn save(&self, id: u64, name: &str) -> Result<(), String>; } // ユースケース: ジェネリクスで依存を受け取る pub struct GetUserUseCase<R: UserRepository> { repo: R, } impl<R: UserRepository> GetUserUseCase<R> { pub fn new(repo: R) -> Self { Self { repo } } pub fn execute(&self, id: u64) -> String { self.repo .find_by_id(id) .unwrap_or_else(|| "unknown".to_string()) } } テスト側でのStub実装 #[cfg(test)] mod tests { use super::*; struct StubUserRepo { users: std::collections::HashMap<u64, String>, } impl UserRepository for StubUserRepo { fn find_by_id(&self, id: u64) -> Option<String> { self.users.get(&id).cloned() } fn save(&self, _id: u64, _name: &str) -> Result<(), String> { Ok(()) } } #[test] fn returns_user_name_when_found() { let mut users = std::collections::HashMap::new(); users.insert(1, "Alice".to_string()); let repo = StubUserRepo { users }; let uc = GetUserUseCase::new(repo); assert_eq!(uc.execute(1), "Alice"); } #[test] fn returns_unknown_when_not_found() { let repo = StubUserRepo { users: std::collections::HashMap::new(), }; let uc = GetUserUseCase::new(repo); assert_eq!(uc.execute(999), "unknown"); } } この手動実装は依存ライブラリが不要で仕組みも明快ですが、traitのメソッド数が増えると、テストに使わないメソッドまで実装する手間が発生します。mockallを使えばこの問題を解決できます。 ...