Rustのmockallクレートを利用してみた。

Rustでモッククレートはいくつかあるが、一番良さそうであったmockallクレートを利用してみた。

https://crates.io/crates/mockall

今回確認した各種バージョン情報

名称 バージョン
Windows10 Pro 1909
Rust 1.47.0
mockall 0.8.3

automockアトリビュートが何をしているのかわからないので、Usageにあるトレイトをcargo expandしてみる

#[cfg(test)]
use mockall::{automock, mock, predicate::*};
#[cfg_attr(test, automock)]
trait MyTrait {
    fn foo(&self, x: u32) -> u32;
}
cargo expand --lib --tests

結果

#[cfg(test)]
use mockall::{automock, mock, predicate::*};
trait MyTrait {
    fn foo(&self, x: u32) -> u32;
}
#[allow(non_snake_case)]
#[allow(missing_docs)]
pub mod __mock_MockMyTrait {
    use super::*;
}
#[allow(non_camel_case_types)]
#[allow(non_snake_case)]
#[allow(missing_docs)]
struct MockMyTrait {
    MyTrait_expectations: MockMyTrait_MyTrait,
}
impl ::std::default::Default for MockMyTrait {
    #[allow(clippy::default_trait_access)]
    fn default() -> Self {
        Self {
            MyTrait_expectations: Default::default(),
        }
    }
}

#[allow(non_snake_case)]
#[allow(missing_docs)]
pub mod __mock_MockMyTrait_MyTrait {
    // 長いので省略
}

#[allow(non_camel_case_types)]
#[allow(non_snake_case)]
#[allow(missing_docs)]
struct MockMyTrait_MyTrait {
    foo: __mock_MockMyTrait_MyTrait::__foo::Expectations,
}
impl ::std::default::Default for MockMyTrait_MyTrait {
    fn default() -> Self {
        Self {
            foo: Default::default(),
        }
    }
}
impl MockMyTrait_MyTrait {
    /// Validate that all current expectations for all methods have
    /// been satisfied, and discard them.
    pub fn checkpoint(&mut self) {
        {
            self.foo.checkpoint();
        }
    }
}
impl MockMyTrait {
    /// Validate that all current expectations for all methods have
    /// been satisfied, and discard them.
    pub fn checkpoint(&mut self) {
        self.MyTrait_expectations.checkpoint();
    }
    /// Create a new mock object with no expectations.
    ///
    /// This method will not be generated if the real struct
    /// already has a `new` method.  However, it *will* be
    /// generated if the struct implements a trait with a `new`
    /// method.  The trait's `new` method can still be called
    /// like `<MockX as TraitY>::new`
    pub fn new() -> Self {
        Self::default()
    }
}
impl MyTrait for MockMyTrait {
    fn foo(&self, x: u32) -> u32 {
        self.MyTrait_expectations
            .foo
            .call(x)
            .expect("MockMyTrait::foo: No matching expectation found")
    }
}
impl MockMyTrait {
    #[must_use = "Must set return value when not using the \"nightly\" feature"]
    ///Create an [`Expectation`](__mock_MockMyTrait_MyTrait/__foo/struct.Expectation.html) for mocking the `foo` method
    fn expect_foo(&mut self) -> &mut __mock_MockMyTrait_MyTrait::__foo::Expectation {
        self.MyTrait_expectations.foo.expect()
    }
}

トレイト名に接頭辞Mockを付けた構造体とそれに加えて接尾辞__トレイト名の構造体を実装するようだ。 トレイトメソッドはexpect_メソッド名のメソッドが実装されこれがモックメソッドになるようだ。

基本的には、複数の実装(impl)をする場合は、mockマクロを使い、一つだけの実装であれば、automock属性を使うのが良さそう。

reqwest利用箇所をモック化してみた。

Comparing cec9d1802ecd70deabe52eec958f898867f3d6c3..081591db09244044b2bd8278579edba24bd4cb37 · masinc/checkip-rust · GitHub

基本的にはautomockアトリビュートかmockマクロを使うだけで非常に簡単に実装できる。