Moqによる拡張メソッドのモック


172

既存のインターフェースがあります...

public interface ISomeInterface
{
    void SomeMethod();
}

ミックスインを使用してこのインターフェースを拡張しました...

public static class SomeInterfaceExtensions
{
    public static void AnotherMethod(this ISomeInterface someInterface)
    {
        // Implementation here
    }
}

私はテストしたいこれを呼び出すクラスがあります...

public class Caller
{
    private readonly ISomeInterface someInterface;

    public Caller(ISomeInterface someInterface)
    {
        this.someInterface = someInterface;
    }

    public void Main()
    {
        someInterface.AnotherMethod();
    }
}

インターフェイスをモックして、拡張メソッドの呼び出しを確認するテスト...

    [Test]
    public void Main_BasicCall_CallsAnotherMethod()
    {
        // Arrange
        var someInterfaceMock = new Mock<ISomeInterface>();
        someInterfaceMock.Setup(x => x.AnotherMethod()).Verifiable();

        var caller = new Caller(someInterfaceMock.Object);

        // Act
        caller.Main();

        // Assert
        someInterfaceMock.Verify();
    }

ただし、このテストを実行すると例外が発生します...

System.ArgumentException: Invalid setup on a non-member method:
x => x.AnotherMethod()

私の質問は、ミックスインの呼び出しを模擬する良い方法はありますか?


3
私の経験では、用語mixinと拡張メソッドは別のものです。この場合、後者を使用して、混乱を避けます:P
Ruben Bartelink

回答:


33

モックフレームワークで静的メソッド(したがって拡張メソッド)を「直接」モックすることはできません。Moles(http://research.microsoft.com/en-us/projects/pex/downloads.aspx)は、別のアプローチを実装するMicrosoftの無料ツールです。ツールの説明は次のとおりです。

Molesは、デリゲートに基づく.NETのテストスタブおよび迂回用の軽量フレームワークです。

Molesは、.NETメソッドを迂回するために使用できます。これには、密閉型の非仮想/静的メソッドが含まれます。

Molesを任意のテストフレームワークで使用できます(それは独立しています)。


2
Moles以外にも、.NETのプロファイラAPIを使用してオブジェクトをモックし、呼び出しを置き換えることができる(フリーではない)モッキングフレームワークが他にもあります。私が知っている2つは、TelerikのJustMockTypeMock Isolatorです。
Marcel Gosselin、2011

6
理論的にはほくろは良いですが、試してみると3つの問題があり、使用できませんでした... 1)Resharper NUnitランナーでは実行されません2)スタブ化されたアセンブリごとにほくろアセンブリを手動で作成する必要があります3 )スタブされたメソッドが変更されるたびに、モルアセンブリを手動で再作成する必要があります。
ラッセルギ

26

この問題を回避するためにラッパーを使用しました。ラッパーオブジェクトを作成し、モックメソッドを渡します。

Paul Irwinによる「単体テストの静的メソッドのモック」を参照してください。良い例があります。


12
私がこの答えを気に入っているのは、それを言っているのは(直接言うことなく)、コードを変更してテスト可能にする必要があるからです。それはちょうどそれが機能する方法です。マイクロチップ/ IC / ASIC設計では、これらのチップは機能するように設計されているだけでなく、さらにテストできるように設計されている必要があることを理解してください。作業。ソフトウェアについても同様です。テスト可能なように構築していない場合、それは...役に立たない。テスト可能なようにビルドします。これは、場合によってはコードの書き直し(およびラッパーの使用)を行い、それをテストする自動テストをビルドします。
Michael Plautz 2017

2
Dapper、Dapper.Contrib、IDbConnectionをラップする小さなライブラリを作成しました。github.com/codeapologist/DataAbstractions.Dapper
Drew

15

入力を模擬しようとしていた拡張メソッドの内部を発見し、拡張内部で何が起こっているのかを模擬しなければならないことに気づきました。

メソッドにコードを直接追加する拡張機能を使用して表示しました。つまり、拡張機能自体ではなく、拡張機能の内部で何が起こるかを模擬する必要がありました。


11

実際のインターフェースを継承し、拡張メソッドと同じシグネチャを持つメンバーを持つテストインターフェースを模擬できます。

次に、テストインターフェイスをモックし、実際のインターフェイスをモックに追加して、セットアップでテストメソッドを呼び出すことができます。

モックの実装は、必要なメソッドを呼び出すか、メソッドが呼び出されていることを確認するだけです。

IReal //on which some extension method is defined
{
    ... SomeRegularMethod(...);
}

static ExtensionsForIReal
{
    static ... SomeExtensionMethod(this IReal iReal,...);
}

ITest: IReal
{
    //This is a regular method with same name and signature as the extension without the "this IReal iReal" parameter
    ... SomeExtensionMethod(...);
}

var someMock = new Mock<ITest>();
Mock.As<IReal>(); //ad IReal to the mock
someMock.Setup(x => x.SomeExtensionMethod(...)).Verifiable(); //Calls SomeExtensionMethod on ITest
someMock.As<IReal>().Setup(x => x.SomeRegularMethod(...)).Verifiable(); //Calls SomeRegularMethod on IReal

2つのインターフェースをサポートするモックを実装する方法について、この投稿の HåvardSソリューションに感謝します。それを見つけたら、テストインターフェースと静的メソッドに合わせて調整するのは簡単な作業でした。


あなたは私たちがために同じサンプル署名を喜ば示すことができるSomeNotAnExtensionMethodSomeNotAnExtensionMethod?インターフェイス内に拡張メソッドシグネチャを作成する方法が
わかりました

1
@ピーターCsala:これが不明確な場合は申し訳ありません。投稿をわかりやすくするために更新し、SomeNotAnExtensionMethodの名前をSomeRegularMethodに変更しました。
pasx

の場合、どのようにiRealパラメーターを渡しますか?それを最初のパラメーターとして渡す場合、どのように設定しますか?これにより、ランタイム例外が発生します。SomeExtensionMethodITestSetup( x=> x.SomeExtensionMethod(x, ...)
Peter Csala

あなたはそれを渡しません。モックの観点から見ると、ITestには、コード内の拡張メソッドへの呼び出しのシグネチャと一致するthisパラメータのない通常のメソッドが含まれているため、ここでIRealを受信することはなく、IReal / ITestの実装はモックそのものです。 。SomeExtensionMethodでモック化されたIRealの一部のプロパティにアクセスする場合は、モックですべて実行する必要がありobject _mockCache = whateverます。例:... `Setup(x => x.SomeExtensionMethod(...).. Callback(()=> access _mockCache here);)
pasx

8

JustMockで拡張メソッドを簡単にモックできます。APIは、通常のメソッドのモックと同じです。以下を検討してください

public static string Echo(this Foo foo, string strValue) 
{ 
    return strValue; 
}

この方法を準備して確認するには、次のようにします。

string expected = "World";

var foo = new Foo();
Mock.Arrange(() => foo.Echo(Arg.IsAny<string>())).Returns(expected);

string result = foo.Echo("Hello");

Assert.AreEqual(expected, result);

ここにもドキュメントへのリンクがあります:拡張メソッドのモッキング


2

オブジェクト自体をラップするときは、ラッパー(アダプターパターン)を使用するのが好きです。オブジェクトの一部ではない拡張メソッドをラップするためにそれを使用するかどうかはわかりません。

Action、Func、Predicate、またはdelegateタイプの内部Lazy Injectableプロパティを使用して、ユニットテスト中にメソッドを注入(スワップアウト)できるようにします。

    internal Func<IMyObject, string, object> DoWorkMethod
    {
        [ExcludeFromCodeCoverage]
        get { return _DoWorkMethod ?? (_DoWorkMethod = (obj, val) => { return obj.DoWork(val); }); }
        set { _DoWorkMethod = value; }
    } private Func<IMyObject, string, object> _DoWorkMethod;

次に、実際のメソッドの代わりにFuncを呼び出します。

    public object SomeFunction()
    {
        var val = "doesn't matter for this example";
        return DoWorkMethod.Invoke(MyObjectProperty, val);
    }

より完全な例については、http://www.rhyous.com/2016/08/11/unit-testing-calls-to-complex-extension-methods/を確認してください。


これは良いことですが、読者は_DoWorkMethodがクラスの新しいフィールドであり、クラスの各インスタンスがもう1つのフィールドを割り当てる必要があることに注意してください。これが問題になることはまれですが、一度に割り当てるインスタンスの数によっては、問題になる場合もあります。この問題は、_DoWorkMethodを静的にすることで回避できます。欠点は、単体テストを同時に実行している場合、同じ静的値を変更する可能性のある2つの異なる単体テストが完了することです。
zumalifeguard 2016年

-1

したがって、Moqを使用していて、Extensionメソッドの結果をモックしたい場合は、 SetupReturnsDefault<ReturnTypeOfExtensionMethod>(new ConcreteInstanceToReturn())場合は、モックしようとしているExtensionメソッドを持つモッククラスのインスタンスで。

完璧ではありませんが、単体テストの目的には適しています。


SetReturnsDefault <T>()だと思います
David

具体的なインスタンスを返すことはありません。カスタムc#クラスの場合はnullです!
HelloWorld
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.