web-dev-qa-db-ja.com

Moqを使用して、呼び出しが正しい順序で行われたことを確認する

次のメソッドをテストする必要があります。

_CreateOutput(IWriter writer)
{
    writer.Write(type);
    writer.Write(id);
    writer.Write(sender);

    // many more Write()s...
}
_

Moq'd IWriterを作成しました。Write()メソッドが正しい順序で呼び出されるようにします。

次のテストコードがあります。

_var mockWriter = new Mock<IWriter>(MockBehavior.Strict);
var sequence = new MockSequence();
mockWriter.InSequence(sequence).Setup(x => x.Write(expectedType));
mockWriter.InSequence(sequence).Setup(x => x.Write(expectedId));
mockWriter.InSequence(sequence).Setup(x => x.Write(expectedSender));
_

ただし、Write()CreateOutput()への2回目の呼び出し(id値を書き込むため)は、メッセージ "MockExceptionをスローしますIWriter.Write()の呼び出しは、モックの動作が厳密で失敗しました。モックでのすべての呼び出しには、対応するセットアップが必要です。 ".

Moqシーケンスの決定的で最新のドキュメント/例を見つけるのも難しいと感じています。

何か間違ったことをしているのですか、それとも同じ方法を使用してシーケンスを設定できませんか?そうでない場合、使用できる代替手段はありますか(できればMoq/NUnitを使用)。

53
g t

同じモックでMockSequenceを使用 の場合、バグがあります。 Moqライブラリの今後のリリースで確実に修正されます(Moq.MethodCall.Matches実装を変更して手動で修正することもできます)。

Moqのみを使用する場合は、コールバックを介してメソッド呼び出しの順序を確認できます。

int callOrder = 0;
writerMock.Setup(x => x.Write(expectedType)).Callback(() => Assert.That(callOrder++, Is.EqualTo(0)));
writerMock.Setup(x => x.Write(expectedId)).Callback(() => Assert.That(callOrder++, Is.EqualTo(1)));
writerMock.Setup(x => x.Write(expectedSender)).Callback(() => Assert.That(callOrder++, Is.EqualTo(2)));
58

私は望む動作を得ることができましたが、サードパーティのライブラリを http://dpwhelan.com/blog/software-development/moq-sequences/ からダウンロードする必要があります

次に、以下を使用してシーケンスをテストできます。

var mockWriter = new Mock<IWriter>(MockBehavior.Strict);
using (Sequence.Create())
{
    mockWriter.Setup(x => x.Write(expectedType)).InSequence();
    mockWriter.Setup(x => x.Write(expectedId)).InSequence();
    mockWriter.Setup(x => x.Write(expectedSender)).InSequence();
}

このソリューションを文書化するために、これを回答の一部として追加しましたが、Moq 4.0のみを使用して同様のことを達成できるかどうかにまだ興味があります。

Moqがまだ開発中かどうかはわかりませんが、MockSequenceの問題を修正するか、Moqにmoq-sequences拡張機能を含めることは良いことです。

10
g t

呼び出しの順序に基づいてアサートする拡張メソッドを作成しました。

public static class MockExtensions
{
  public static void ExpectsInOrder<T>(this Mock<T> mock, params Expression<Action<T>>[] expressions) where T : class
  {
    // All closures have the same instance of sharedCallCount
    var sharedCallCount = 0;
    for (var i = 0; i < expressions.Length; i++)
    {
      // Each closure has it's own instance of expectedCallCount
      var expectedCallCount = i;
      mock.Setup(expressions[i]).Callback(
        () =>
          {
            Assert.AreEqual(expectedCallCount, sharedCallCount);
            sharedCallCount++;
          });
    }
  }
}

これは、スコープが設定された変数に関してクロージャーが機能する方法を利用して機能します。 sharedCallCountの宣言は1つしかないため、すべてのクロージャーは同じ変数への参照を持ちます。 expectedCallCountを使用すると、ループの反復ごとに新しいインスタンスがインスタンス化されます(クロージャーで単にiを使用するのではなく)。このように、各クロージャーには、式が呼び出されたときにsharedCallCountと比較するために、自分自身のみにスコープされたiのコピーがあります。

拡張機能の小さなユニットテストを次に示します。このメソッドは、アサーションセクションではなく、セットアップセクションで呼び出されることに注意してください。

[TestFixture]
public class MockExtensionsTest
{
  [TestCase]
  {
    // Setup
    var mock = new Mock<IAmAnInterface>();
    mock.ExpectsInOrder(
      x => x.MyMethod("1"),
      x => x.MyMethod("2"));

    // Fake the object being called in order
    mock.Object.MyMethod("1");
    mock.Object.MyMethod("2");
  }

  [TestCase]
  {
    // Setup
    var mock = new Mock<IAmAnInterface>();
    mock.ExpectsInOrder(
      x => x.MyMethod("1"),
      x => x.MyMethod("2"));

    // Fake the object being called out of order
    Assert.Throws<AssertionException>(() => mock.Object.MyMethod("2"));
  }
}

public interface IAmAnInterface
{
  void MyMethod(string param);
}
8
Justin Ryder

最近、Moqの2つの機能、VerifyInSequence()とVerifyNotInSequence()をまとめました。 Loose Mocksでも動作します。ただし、これらはmoqリポジトリフォークでのみ使用可能です。

https://github.com/grzesiek-galezowski/moq4

公式のmoqリリースに含めることができるかどうかを決定する前に、さらにコメントやテストを待っています。ただし、ソースをZipとしてダウンロードし、dllにビルドして試してみることを妨げるものはありません。これらの機能を使用すると、必要なシーケンス検証は次のように記述できます。

 var mockWriter = new Mock <IWriter>(){CallSequence = new LooseSequence()}; 
 
 //必要な呼び出しを実行します
 
 mockWriter.VerifyInSequence(x => x.Write(expectedType)); 
 mockWriter.VerifyInSequence(x => x.Write(expectedId)); 
 mockWriter.VerifyInSequence(x => x.Write (expectedSender)); 

(必要に応じて、他の2つのシーケンスを使用できることに注意してください。ルーズシーケンスは、検証したいもの間の呼び出しを許可します。StrictSequenceはこれを許可せず、StrictAnytimeSequenceはStrictSequenceに似ています任意の数の任意の呼び出しが先行するシーケンス。

この実験的な機能を試してみることにした場合は、次の点についてコメントしてください: https://github.com/Moq/moq4/issues/21

ありがとう!

4

最も簡単な解決策は、 Queue を使用することです。

var expectedParameters = new Queue<string>(new[]{expectedType,expectedId,expectedSender});
mockWriter.Setup(x => x.Write(expectedType))
          .Callback((string s) => Assert.AreEqual(expectedParameters.Dequeue(), s));

ExpectedIdは期待したものではないと思う。

ただし、この場合に確認するために、おそらく独自のIWriterの実装を作成するだけです...おそらくはるかに簡単です(そして後で簡単に変更できます)。

Moqのアドバイスは直接ありません。私はそれを愛していますが、それをこれでやっていません。

各セットアップの最後に.Verify()を追加する必要がありますか? (私は恐れているけれども、それは本当に推測です)。

0
John Nicholas