web-dev-qa-db-ja.com

Moqを使用した拡張メソッドのモック

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

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()

私の質問は、mixin呼び出しをモックアウトする良い方法はありますか?

149

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

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

モールは、シールタイプの非仮想/静的メソッドを含む、あらゆる.NETメソッドを迂回するために使用できます。

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

30

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

単体テスト用の静的メソッドのモック Paul Irwinによる、素敵な例があります。

18
Alvis

入力をモックしようとしている拡張メソッドの内部を発見し、拡張内で何が起こっているかをモックする必要があることがわかりました。

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

9
chris31389

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

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

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

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

ITest: IReal
{
    ... 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.SomeNotAnExtensionMethod()).Verifiable(); //Calls SomeNotAnExtensionMethod on IReal

this post のHåvardSソリューションに感謝しますそれを見つけたら、テストインターフェースと静的メソッドでそれを適応させるのは簡単なことでした。

3
pasx

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

Action、Func、Predicate、delegateのいずれかのタイプの内部Lazy Injectable Propertyを使用し、単体テスト中にメソッドを挿入(スワップアウト)できるようにします。

    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/ をご覧ください。

2
Rhyous