Moqを使用した特定のパラメーターの確認


169
public void SubmitMessagesToQueue_OneMessage_SubmitSuccessfully()
{
    var messageServiceClientMock = new Mock<IMessageServiceClient>();
    var queueableMessage = CreateSingleQueueableMessage();
    var message = queueableMessage[0];
    var xml = QueueableMessageAsXml(queueableMessage);
    messageServiceClientMock.Setup(proxy => proxy.SubmitMessage(xml)).Verifiable();
    //messageServiceClientMock.Setup(proxy => proxy.SubmitMessage(It.IsAny<XmlElement>())).Verifiable();

    var serviceProxyFactoryStub = new Mock<IMessageServiceClientFactory>();
    serviceProxyFactoryStub.Setup(proxyFactory => proxyFactory.CreateProxy()).Returns(essageServiceClientMock.Object);
    var loggerStub = new Mock<ILogger>();

    var client = new MessageClient(serviceProxyFactoryStub.Object, loggerStub.Object);
    client.SubmitMessagesToQueue(new List<IMessageRequestDTO> {message});

    //messageServiceClientMock.Verify(proxy => proxy.SubmitMessage(xml), Times.Once());
    messageServiceClientMock.Verify();
}

Moqを使い始めて、少し苦労しています。messageServiceClientがXmlElementである正しいパラメーターを受け取っていることを確認しようとしていますが、それを機能させる方法が見つかりません。特定の値をチェックしない場合にのみ機能します。

何か案は?

部分的な答え:プロキシに送信されたxmlが正しいことをテストする方法を見つけましたが、それでも正しい方法だとは思いません。

public void SubmitMessagesToQueue_OneMessage_SubmitSuccessfully()
{
    var messageServiceClientMock = new Mock<IMessageServiceClient>();
    messageServiceClientMock.Setup(proxy => proxy.SubmitMessage(It.IsAny<XmlElement>())).Verifiable();
    var serviceProxyFactoryStub = new Mock<IMessageServiceClientFactory>();
    serviceProxyFactoryStub.Setup(proxyFactory => proxyFactory.CreateProxy()).Returns(messageServiceClientMock.Object);
    var loggerStub = new Mock<ILogger>();

    var client = new MessageClient(serviceProxyFactoryStub.Object, loggerStub.Object);
    var message = CreateMessage();
    client.SubmitMessagesToQueue(new List<IMessageRequestDTO> {message});

    messageServiceClientMock.Verify(proxy => proxy.SubmitMessage(It.Is<XmlElement>(xmlElement => XMLDeserializer<QueueableMessage>.Deserialize(xmlElement).Messages.Contains(message))), Times.Once());
}

ちなみに、どうすればVerify呼び出しから式を抽出できますか?

回答:


250

検証ロジックが重要な場合、大きなラムダメソッドを作成するのは面倒です(例を示します)。すべてのテストステートメントを別のメソッドに配置することもできますが、テストコードを読み取るフローが中断されるため、これを行うのは好きではありません。

もう1つのオプションは、セットアップ呼び出しでコールバックを使用して、モックされたメソッドに渡された値を保存し、Assertそれを検証するための標準メソッドを記述することです。例えば:

// Arrange
MyObject saveObject;
mock.Setup(c => c.Method(It.IsAny<int>(), It.IsAny<MyObject>()))
        .Callback<int, MyObject>((i, obj) => saveObject = obj)
        .Returns("xyzzy");

// Act
// ...

// Assert
// Verify Method was called once only
mock.Verify(c => c.Method(It.IsAny<int>(), It.IsAny<MyObject>()), Times.Once());
// Assert about saveObject
Assert.That(saveObject.TheProperty, Is.EqualTo(2));

6
このアプローチの大きな利点の1つは、オブジェクトをどのようにして不正確にするかについて、特定のテストエラーが発生することです(それぞれ個別にテストしているため)。
ロブ教会

1
私がこれをしたのは私だけだと思いました、それが合理的なアプローチであることを見てうれしいです!
Applebyは2015年

3
MayoによるIt.Is <MyObject>(validator)の使用は、パラメータ値をラムダの一部として保存する少し厄介な方法を回避するのでより良いと思います
stevec

たとえば、テストを並行して実行する場合、このスレッドは安全ですか?
アントントルケン

@AntonTolken試したことはありませんが、私の例では、更新されるオブジェクトはローカル変数(saveObject)なので、スレッドセーフにする必要があります。
リッチテブ

112

私は同じ方法で呼び出しを確認してきました-私はそれを行う正しい方法だと思います。

mockSomething.Verify(ms => ms.Method(
    It.IsAny<int>(), 
    It.Is<MyObject>(mo => mo.Id == 5 && mo.description == "test")
  ), Times.Once());

ラムダ式が扱いにくくなったらMyObject、入力と出力を受け取る関数を作成することができますtrue/ false...

mockSomething.Verify(ms => ms.Method(
    It.IsAny<int>(), 
    It.Is<MyObject>(mo => MyObjectFunc(mo))
  ), Times.Once());

private bool MyObjectFunc(MyObject myObject)
{
  return myObject.Id == 5 && myObject.description == "test";
}

また、メソッドがまったく呼び出されなかったときにメソッドが複数回呼び出されたというエラーメッセージが表示されるMockのバグにも注意してください。彼らはすでにそれを修正しているかもしれませんが、そのメッセージが表示された場合は、メソッドが実際に呼び出されたことを確認することを検討してください。

編集:これは、リスト内の各オブジェクトの関数を呼び出すことを確認するシナリオ(たとえば)で、verifyを複数回呼び出す例です。

foreach (var item in myList)
  mockRepository.Verify(mr => mr.Update(
    It.Is<MyObject>(i => i.Id == item.Id && i.LastUpdated == item.LastUpdated),
    Times.Once());

セットアップの同じアプローチ...

foreach (var item in myList) {
  var stuff = ... // some result specific to the item
  this.mockRepository
    .Setup(mr => mr.GetStuff(item.itemId))
    .Returns(stuff);
}

そのため、そのItemIdに対してGetStuffが呼び出されるたびに、そのアイテムに固有のものを返します。または、itemIdを入力として取得してデータを返す関数を使用することもできます。

this.mockRepository
    .Setup(mr => mr.GetStuff(It.IsAny<int>()))
    .Returns((int id) => SomeFunctionThatReturnsStuff(id));

しばらく前にブログで見たもう1つの方法(おそらくフィールハアック?)は、ある種のデキューオブジェクトから戻るように設定していました。関数が呼び出されるたびに、キューからアイテムをプルしていました。


1
ありがとう、それは私には理にかなっています。それでも理解できないのは、セットアップまたは検証でいつ詳細を指定するかです。それはかなり混乱しています。現時点では、セットアップで何でも許可し、Verifyで値を指定しています。
Luis Mirabal

複数の通話がある場合にメッセージを確認するにはどうすればよいと思いますか?クライアントはメッセージを受け取り、複数のqueueableMessagesを作成できます。これにより、複数の呼び出しが発生し、各呼び出しで異なるメッセージをチェックする必要があります。私はまだ一般的に単体テストに苦労していますが、まだあまり詳しくありません。
Luis Mirabal

あなたがこれをどうやってやるべきかという点で魔法の特効薬はないと思います。それには練習が必要で、上達し始めます。私にとっては、比較する対象があり、そのパラメーターを別のテストでまだテストしていない場合にのみ、パラメーターを指定します。複数の呼び出しについては、いくつかのアプローチがあります。複数回呼び出される関数を設定および検証するために、私は通常、期待する呼び出しごとにセットアップまたは検証(Times.Once())を呼び出します-forループを使用することがよくあります。特定のパラメーターを使用して、各呼び出しを分離できます。
Mayo

複数の通話の例をいくつか追加しました-上記の回答を参照してください。
Mayo

1
「また、Mockのバグに注意してください。エラーメッセージには、メソッドがまったく呼び出されなかったときに複数回呼び出されたことが示されています。現時点で修正されている可能性があります。ただし、そのメッセージが表示された場合は、メソッドが実際に呼び出されました。」-このようなバグは、モックライブラリIMHOを完全に無効にします。皮肉なことに、適切なテストコードがなかった:-)
Gianluca Ghettini

20

より簡単な方法は次のようにすることです:

ObjectA.Verify(
    a => a.Execute(
        It.Is<Params>(p => p.Id == 7)
    )
);

これが機能していないようです。Code.WRCCをパラメーターとして使用してメソッドが呼び出されたことを確認しようとしています。しかし、私のテストでは、常に渡されたパラメータがWRDDであっても、渡す... m.Setup(x => x.CreateSR(Code.WRDD)).ReturnsAsync(0); await ClassUnderTest.CompleteCC(accountKey, It.IsAny<int>(), mockRequest); m.Verify(x => x.CreateSR(It.Is<Code>(p => p == Code.WRCC)), Times.Once());
グレッグ・クイン

1

Moqが平等をチェックするという事実に問題があると思います。また、XmlElementはEqualsをオーバーライドしないため、その実装は参照の等価性をチェックします。

カスタムオブジェクトを使用できないので、equalsをオーバーライドできますか?


はい、私はそれをやった。問題はXmlのチェックであることがわかりました。質問の2番目の部分で、オブジェクトにXMLをデシリアライズする可能な回答を追加しました
Luis Mirabal

1

これらのいずれかもありましたが、アクションのパラメーターはパブリックプロパティのないインターフェイスでした。最終的には別のメソッドでIt.Is()を使用し、このメソッド内でインターフェイスのモックを行う必要がありました

public interface IQuery
{
    IQuery SetSomeFields(string info);
}

void DoSomeQuerying(Action<IQuery> queryThing);

mockedObject.Setup(m => m.DoSomeQuerying(It.Is<Action<IQuery>>(q => MyCheckingMethod(q)));

private bool MyCheckingMethod(Action<IQuery> queryAction)
{
    var mockQuery = new Mock<IQuery>();
    mockQuery.Setup(m => m.SetSomeFields(It.Is<string>(s => s.MeetsSomeCondition())
    queryAction.Invoke(mockQuery.Object);
    mockQuery.Verify(m => m.SetSomeFields(It.Is<string>(s => s.MeetsSomeCondition(), Times.Once)
    return true
}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.