web-dev-qa-db-ja.com

Moqでクラスをモックするとき、特定のメソッドだけを呼び出すにはどうすればよいですか?

期待値が設定されていない場合にデフォルト値を返すMoqのLooseモック動作に本当に感謝しています。これは便利でコードを節約し、安全対策としても機能します。依存関係は、単体テスト中に(仮想である限り)意図せずに呼び出されることはありません。

ただし、テスト対象のメソッドがたまたま仮想である場合に、これらの利点を維持する方法について混乱しています。
この場合、私doは、その1つのメソッドの実際のコードを呼び出しながら、クラスの残りの部分を大まかにモックします。

検索で見つけたのは、メソッドが確実に呼び出されるように_mock.CallBase = true_を設定できることだけです。ただし、それはクラス全体に影響します。呼び出しの依存関係を隠すクラス内の他のすべてのプロパティとメソッドに関するジレンマに陥るので、私はそれをしたくありません。CallBaseがtrueの場合、次のいずれかを行う必要があります。

  1. 依存関係を隠すすべてのプロパティとメソッドのセットアップスタブ-私のテストでは、これらの依存関係に注意する必要があるとは考えていませんが、
  2. スタブをセットアップすることを忘れないでください(将来、新しい依存関係がコードに追加されないことを願っています)-実際の依存関係にぶつかるリスクユニットテスト。

私が欲しいと思うのは次のようなものです:
mock.Setup(m => m.VirtualMethod()).CallBase();
mock.Object.VirtualMethod()を呼び出すと、Moqが実際の実装を呼び出します...

Q:Moqを使用して、クラスをモックしていくつかの依存関係のみをスタブ化するときに仮想メソッドをテストする方法はありますか?つまり、CallBase = trueそして、すべての依存関係をスタブ化する必要がありますか?


説明するサンプルコード
(MSTest、InternalsVisibleTo DynamicProxyGenAssembly2を使用)

次の例では、TestNonVirtualMethodは成功しますが、TestVirtualMethodは失敗します-nullを返します。

_public class Foo
{
    public string NonVirtualMethod() { return GetDependencyA(); }
    public virtual string VirtualMethod() { return GetDependencyA();}

    internal virtual string GetDependencyA() { return "! Hit REAL Dependency A !"; }
    // [... Possibly many other dependencies ...]
    internal virtual string GetDependencyN() { return "! Hit REAL Dependency N !"; }
}

[TestClass]
public class UnitTest1
{
    [TestMethod]
    public void TestNonVirtualMethod()
    {
        var mockFoo = new Mock<Foo>();
        mockFoo.Setup(m => m.GetDependencyA()).Returns(expectedResultString);

        string result = mockFoo.Object.NonVirtualMethod();

        Assert.AreEqual(expectedResultString, result);
    }

    [TestMethod]
    public void TestVirtualMethod() // Fails
    {
        var mockFoo = new Mock<Foo>();
        mockFoo.Setup(m => m.GetDependencyA()).Returns(expectedResultString);
        // (I don't want to setup GetDependencyB ... GetDependencyN here)

        string result = mockFoo.Object.VirtualMethod();

        Assert.AreEqual(expectedResultString, result);
    }

    string expectedResultString = "Hit mock dependency A - OK";
}
_
42
Daryn

Lunivoreの回答は、書かれた時点では正しかったと思います。

Moqの新しいバージョン(2013年のバージョン4.1以降)では、提案された正確な構文を使用して必要なことを実行できます。あれは:

_mock.Setup(m => m.VirtualMethod()).CallBase();
_

これは、単にdefault(WhatEver)を返すのではなく、VirtualMethodの基本実装を呼び出すようにルーズモックを設定しますが、このメンバー(VirtualMethod)に対してのみです。


ユーザーBornToCodeがコメントに記しているように、メソッドにreturn type voidがある場合、これは機能しません。 VirtualMethodが非voidの場合、Setup呼び出しは_Moq.Language.Flow.ISetup<TMock, TResult>_からCallBase()メソッドを継承する_Moq.Language.Flow.IReturns<TMock, TResult>_を提供します。しかし、メソッドがvoidの場合は、代わりに_Moq.Language.Flow.ISetup<TMock>_を取得しますが、これには必要なCallBase()メソッドがありません。

pdate: andrew.rockwellによると、これはvoidメソッドで機能するようになり、バージョン4.10(2018以降)で修正されたようです。

16

誰もがこの質問に何年も答えていないので、答えるに値すると思うので、私はあなたが尋ねた最高レベルの質問に焦点を当てます:テスト中のメソッドが偶然仮想である場合にこれらの利点を維持する方法。

簡単な答え:Moqでそれを行うことはできません。少なくとも直接はできません。しかし、あなたはそれを行うことができます。

行動に2つの側面があるとします。側面Aは仮想で、側面Bはそうではありません。これは、クラスの内容を反映しています。 Bは他の方法を使用できるかどうか。それはあなた次第です。

現時点では、あなたのクラスFooはAとBの2つのことを行っています。AをモックしてBを独自にテストしたいという理由だけで、それらは別々の責任であると言えます。

他のものをモックアウトせずに仮想メソッドをモックアウトしようとする代わりに、次のことができます。

  • 動作Aを別のクラスに移動する
  • 依存関係は、Aの新しいクラスをFooからFooのコンストラクターに注入します
  • bからそのクラスを呼び出します。

これで、Aをモックし、実際のAを実際に呼び出すことなく、B..Nの実際のコードを呼び出すことができます。Aを仮想に保つか、インターフェースを介してアクセスして、それをモックすることができます。これも単一責任原則に沿ったものです。

Fooを使用してコンストラクターをカスケードできます-コンストラクターFoo()にコンストラクターFoo(new A())を呼び出します-これを行うために依存性注入フレームワークも必要ありません。

お役に立てれば!

8
Lunivore