Moq:モックサービスのメソッドに渡されたパラメーターにアクセスする方法


169

このクラスを想像してください

public class Foo {

    private Handler _h;

    public Foo(Handler h)
    {
        _h = h;
    }

    public void Bar(int i)
    {
        _h.AsyncHandle(CalcOn(i));
    }

    private SomeResponse CalcOn(int i)
    {
        ...;
    }
}

FooのテストでのハンドラーのMo(q)cking、Bar()渡されたものを確認するにはどうすればよい_h.AsyncHandleですか?


「AsyncHandle」(余分な「n」)を意味しましたか?ハンドラーのコードを投稿したり、標準型の場合は完全修飾型名を指定したりできますか?
TrueWill、

スケルトンテストを表示して、考えていることを示すことができますか?あなたの側からは明らかだと思いますが、私たちの側からは、長い推測的な答えをせずに質問を答えられるようにするのに時間をかけなかった人のように見えます。
Ruben Bartelink 2010

1
FooもBar()もこのようなものもありません。これは、アプリケーションの詳細に邪魔されることなく、私が置かれている状況を示すための単なるデモコードです。そして、私はちょうど答えを得ました、私は得ることを望んでいました。

回答:


283

Mock.Callback-methodを使用できます。

var mock = new Mock<Handler>();
SomeResponse result = null;
mock.Setup(h => h.AnsyncHandle(It.IsAny<SomeResponse>()))
    .Callback<SomeResponse>(r => result = r);

// do your test
new Foo(mock.Object).Bar(22);
Assert.NotNull(result);

渡された引数で単純なものだけをチェックしたい場合は、直接行うこともできます。

mock.Setup(h => h.AnsyncHandle(It.Is<SomeResponse>(response => response != null)));

36
Callback<>()ちなみに、関数に複数の引数がある場合は、ジェネリックMoqメソッドですべての型を指定する必要があります。たとえば、メソッドに定義Handler.AnsyncHandle(string, SomeResponse)がある場合は、が必要になり/* ... */.Callback<string, SomeResponse>(r => result = r);ます。多くの場所でこれが明確に述べられているのを見つけられなかったので、ここに追加すると思いました。
フランクブライス

12
@JavaJudtの回答を見たことがない場合に備えて、@ Frankを修正したかっただけです。2引数を取得するための適切な方法は次のとおりです。/* ... */.Callback<string, SomeResponse>((s1, s2) => { str1 = s1; result = s2});
renatogbp

2
:あなたはまた、単にそのように、ラムダ式に引数の型を宣言することで同じことを達成することができます.Callback((string s1, SomeResponse s2) => /* stuff */ )
jb637

1
組み込みCapture.Inヘルパーへの参照を含めるように応答を更新していただけませんか?
cao

29

Gamlorの答えは私にとってはうまくいきましたが、複数のパラメーターを含む解決策を探していたので、John Carpenterのコメントを拡張すると思いました。私はこのページに出くわした他の人々が同じような状況にあるかもしれないと考えました。Moqのドキュメントでこの情報を見つけました。

Gamlorの例を使用しますが、AsyncHandleメソッドが2つの引数を取ると仮定stringSomeResponseます。

var mock = new Mock<Handler>();
string stringResult = string.Empty;
SomeResponse someResponse = null;
mock.Setup(h => h.AsyncHandle(It.IsAny<string>(), It.IsAny<SomeResponse>()))
    .Callback<string, SomeResponse>((s, r) => 
    {
        stringResult = s;
        someResponse = r;
    });

// do your test
new Foo(mock.Object).Bar(22);
Assert.AreEqual("expected string", stringResult);
Assert.IsNotNull(someResponse);

基本的にIt.IsAny<>()は、適切なタイプの別のタイプを追加し、Callbackメソッドに別のタイプを追加し、ラムダ式を適切に変更するだけです。


22

Callbackメソッドは確かに機能しますが、多くのパラメーターを持つメソッドでこれを行うと、少し冗長になる可能性があります。これは、ボイラープレートの一部を削除するために使用したものです。

var mock = new Mock<Handler>();

// do your test   
new Foo(mock.Object).Bar(22);

var arg = new ArgumentCaptor<SomeResponse>();
mock.Verify(h => h.AsyncHandle(arg.Capture()));
Assert.NotNull(arg.Value);

ArgumentCaptorのソースは次のとおりです。

public class ArgumentCaptor<T>
{
    public T Capture()
    {
        return It.Is<T>(t => SaveValue(t));
    }

    private bool SaveValue(T t)
    {
        Value = t;
        return true;
    }

    public T Value { get; private set; }
}

21

Gamlorの答えはうまくいきますが、それを行う別の方法(そしてテストでより表現力があると私が考える方法)は...

var mock = new Mock<Handler>();
var desiredParam = 47; // this is what you want to be passed to AsyncHandle
new Foo(mock.Object).Bar(22);
mock.Verify(h => h.AsyncHandle(desiredParam), Times.Once());

Verifyは非常に強力であり、慣れるまでに時間をかける価値があります。


15
メソッドが既知のパラメーターで呼び出されたかどうかを確認したいだけなら、このアプローチは問題ありません。テストを作成する時点でパラメーターがまだ作成されていない場合(たとえば、問題のユニットがパラメーターを内部で作成している場合)、コールバックを使用すると、これをキャプチャして問い合わせることができますが、アプローチではできません。
Michael 14

1
渡されたオブジェクトのセットすべてを確認する必要があるため、渡された値を保存する必要があります。
MrFox 2016

また、場合によってはパラメーターがエンティティーであり、エンティティーのフィールドごとに個別のアサートが必要な場合があります。そのための最良の方法は、Verifyとマッチャーを使用するのではなく、パラメーターをキャプチャーすることです。
Kevin Wong

12

代わりCapture.Inに、の機能を使用することもできますmoq。これはmoq、コレクションでの引数のキャプチャを可能にするOOTB 機能です。

//Arrange
var args = new List<SomeResponse>();
mock.Setup(h => h.AnsyncHandle(Capture.In(args)));

//Act
new Foo(mock.Object).Bar(22);

//Assert
//... assert args.Single() or args.First()

1
正解です。この答えを見るまで、Moq.CaptureクラスとMoq.CaptureMatchクラスを知りませんでした。キャプチャはCallbackIMOのより良い代替手段です。Captureをパラメーターリストで直接使用するため、メソッドのパラメーターリストをリファクタリングするときに問題が発生する可能性がはるかに低くなり、テストの脆弱性が軽減されます。Callbackでは、セットアップで渡されるパラメーターをCallbackに使用される型パラメーターと同期させておく必要があり、過去に問題が発生したことは間違いありません。
ジャスティンホルツァー

7

あなたはIt.Is<TValue>()マッチャーを使うことができます。

var mock = new Mock<Handler>();
new Foo(mock.Object).Bar(22);
mock.Verify(h => h.AsyncHandle(It.Is<SomeResponse>(r => r != null )));

2

これも機能します:

Mock<InterfaceThing> mockedObject = new Mock<InterfaceThing>();
var objectParameter = mockedObject.Invocations[1].Arguments[0] as ObjectParameter;

2

ここにたくさんの良い答えがあります!依存関係に渡されるいくつかのクラスパラメーターについてアサーションを作成する必要があるまで、そのままの状態のMoq機能セットを使用します。ただし、そのような状況に陥った場合、It.Isマッチャーを使用したMoq検証機能はテストの失敗を特定するのに適しておらず、引数を取得するReturn / Callbackの方法は、不要なコード行をテストに追加します(そして長いテストは私にとっては大丈夫です)。

ここに要点があります:https : //gist.github.com/Jacob-McKay/8b8d41ebb9565f5fca23654fd944ac6bとMoq(4.12)拡張を使用して、前述の欠点なしにモックに渡される引数についてアサーションを作成するより宣言的な方法を提供します。確認セクションは次のようになります。

        mockDependency
            .CheckMethodWasCalledOnce(nameof(IExampleDependency.PersistThings))
            .WithArg<InThing2>(inThing2 =>
            {
                Assert.Equal("Input Data with Important additional data", inThing2.Prop1);
                Assert.Equal("I need a trim", inThing2.Prop2);
            })
            .AndArg<InThing3>(inThing3 =>
            {
                Assert.Equal("Important Default Value", inThing3.Prop1);
                Assert.Equal("I NEED TO BE UPPER CASED", inThing3.Prop2);
            });

Moqが同じことを達成する機能を提供しながら、宣言型であり、これが行う障害の分離を提供するなら、私は感激します。成功を祈っている!


1
私はこれが好き。Verify of Moqは、アサートを実行する権限についてxUnitのアサートと競合します。これは、セットアップのMoq部分では正しくありません。It.Is機能のセットアップは、非式もサポートする必要があります。
Thomas

「Moqは、アサートを実行する権限についてxUnitのアサートと競合します」-@Thomasはよく言っています。私はそれが競争に負けるだろうと付け加えます。Moqは、一致する呼び出しがあったかどうかを通知するのが得意ですが、特定のアサートははるかに優れた情報を提供します。私のアプローチの主な欠点は、型の安全性とパラメーターの順序チェックを失うことです。私はしばらくこれに対する改善を探していました、うまくいけば、例を一緒にハックできるC#忍者がそこにいます!そうでなければ、私はこれを更新する方法を見つけた場合。
Jacob McKay
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.