69

次の方法をテストする必要があります。

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()inへの 2 番目の呼び出しでは、「IWriter.Write() 呼び出しがモック動作 Strict で失敗しました。モックに対するすべての呼び出しには、対応するセットアップが必要です。」というメッセージがスローされます。CreateOutput()idMockException

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

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

4

10 に答える 10

76

同じモックで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)));
于 2012-05-15T22:08:46.037 に答える
11

希望する動作を得ることができましたが、 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 拡張機能を含めるとよいでしょう。

于 2012-05-15T14:33:15.833 に答える
10

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

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);
}
于 2013-10-31T16:11:35.737 に答える
6

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

var expectedParameters = new Queue<string>(new[]{expectedType,expectedId,expectedSender});
mockWriter.Setup(x => x.Write(expectedType))
          .Callback((string s) => Assert.AreEqual(expectedParameters.Dequeue(), s));
于 2016-09-25T23:25:41.520 に答える
5

最近、Moqの2つの機能、VerifyInSequence()とVerifyNotInSequence()をまとめました。彼らはルーズモックでも動作します。ただし、これらは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であなたの考えをコメントしてください。

ありがとう!

于 2012-12-29T16:59:58.190 に答える
2

同様のシナリオがあり、受け入れられた回答に触発されて、次のアプローチを使用しました。

//arrange
var someServiceToTest = new SomeService();

var expectedCallOrder = new List<string>
{
    "WriteA",
    "WriteB",
    "WriteC"
};
var actualCallOrder = new List<string>();

var mockWriter = new Mock<IWriter>();
mockWriter.Setup(x => x.Write("A")).Callback(() => { actualCallOrder.Add("WriteA"); });
mockWriter.Setup(x => x.Write("B")).Callback(() => { actualCallOrder.Add("WriteB"); });
mockWriter.Setup(x => x.Write("C")).Callback(() => { actualCallOrder.Add("WriteC"); });

//act
someServiceToTest.CreateOutput(_mockWriter.Object);

//assert
Assert.AreEqual(expectedCallOrder, actualCallOrder);
于 2020-12-31T15:20:22.047 に答える
0

expectedId はあなたが期待するものではないと思います。

ただし、この場合、検証するために IWriter の独自の実装を作成するだけでよいでしょう...おそらくはるかに簡単です(後で変更するのも簡単です)。

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

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

于 2012-05-15T14:03:55.000 に答える