web-dev-qa-db-ja.com

Moqでモック検証をリセットしますか?

セットアップ:

_public interface IFoo
{
    void Fizz();
}

[Test]
public void A()
{
    var foo = new Mock<IFoo>(MockBehavior.Loose);

    foo.Object.Fizz();

    foo.Verify(x => x.Fizz());

    // stuff here

    foo.Verify(x => x.Fizz(), Times.Never()); // currently this fails
}
_

基本的に、foo.Verify(x => x.Fizz(), Times.Never())を渡すために_// stuff here_にコードを入力したいと思います。

そして、これはおそらくmoq/unit testingの乱用を構成するので、私の正当化は次のようなことができるようにすることです:

_[Test]
public void Justification()
{
    var foo = new Mock<IFoo>(MockBehavior.Loose);
    foo.Setup(x => x.Fizz());

    var objectUnderTest = new ObjectUnderTest(foo.Object);

    objectUnderTest.DoStuffToPushIntoState1(); // this is various lines of code and setup

    foo.Verify(x => x.Fizz());

    // reset the verification here

    objectUnderTest.DoStuffToPushIntoState2(); // more lines of code

    foo.Verify(x => x.Fizz(), Times.Never());
}
_

基本的に、Stateオブジェクトをプッシュするには、かなりの量の作業(さまざまなモックオブジェクトの作成とその他の処理)が必要な状態オブジェクトがあります。次に、State1からState2への移行をテストします。コードを複製または抽象化する代わりに、State1テストを再利用し、State2にプッシュしてアサートを実行します。検証呼び出し以外はすべて実行できます。

52
fostandy

このようなモックをリセットできるとは思わない。代わりに、状態1に移行するときにFizzを1回呼び出す必要があることがわかっている場合は、次のように検証できます。

objectUnderTest.DoStuffToPushIntoState1();
foo.Verify(x => x.Fizz(), Times.Once());  // or however many times you expect it to be called

objectUnderTest.DoStuffToPushIntoState2();
foo.Verify(x => x.Fizz(), Times.Once());

そうは言っても、このために2つの個別のテストを作成します。 2つのテストとして、状態1への遷移が失敗しているか、状態2への遷移が失敗しているかを簡単に確認できます。さらに、このように一緒にテストすると、状態1への移行が失敗した場合、テストメソッドは終了し、状態2への移行はテストされません。

編集

この例として、xUnitで次のコードをテストしました。

[Fact]
public void Test()
{
    var foo = new Mock<IFoo>(MockBehavior.Loose);

    foo.Object.Fizz();
    foo.Verify(x => x.Fizz(), Times.Once(), "Failed After State 1");

    // stuff here
    foo.Object.Fizz();
    foo.Verify(x => x.Fizz(), Times.Once(), "Failed after State 2"); 
}

このテストは、「状態2の後で失敗しました」というメッセージで失敗します。これは、fooをState 2にプッシュするメソッドがFizzを呼び出すとどうなるかをシミュレートしています。存在する場合、2番目のVerifyは失敗します。

コードをもう一度見ると、1つのメソッドを呼び出してそれがモックの別のメソッドを呼び出すかどうかを確認するため、CallBasetrueに設定して、ベースDoStuffToPushIntoState2は、モックのオーバーライドではなく呼び出されます。

7
Jeff Ogata

この投稿が作成されてからずっと後に、OPが要求していた機能が追加されたと思います。Moq.MockExtensions.ResetCalls()というMoq拡張メソッドがあります。

この方法を使用すると、次のように希望どおりに実行できます。

[Test]
public void Justification()
{
    var foo = new Mock<IFoo>(MockBehavior.Loose);
    foo.Setup(x => x.Fizz());

    var objectUnderTest = new ObjectUnderTest(foo.Object);

    objectUnderTest.DoStuffToPushIntoState1(); // this is various lines of code and setup

    foo.Verify(x => x.Fizz());

    foo.ResetCalls(); // *** Reset the verification here with this glorious method ***

    objectUnderTest.DoStuffToPushIntoState2(); // more lines of code

    foo.Verify(x => x.Fizz(), Times.Never());
}

更新

.ResetCalls()の代わりに、ライブラリの最新バージョンで.Invocations.Clear()を使用する必要があります。

foo.Invocations.Clear()
108
stackunderflow

また、MoQを使用したユニットテストでTimes.Exactly(1)検証エラーが発生し、「was called 2 times」エラーメッセージが表示されました。私はこれをMoQのバグと考えています。テストを実行するたびにクリーンなモック状態が期待されるからです。

私の回避策は、テストセットアップで新しいモックインスタンスとテストターゲットを割り当てることでした。

private Mock<IEntityMapper> entityMapperMock;
private OverdraftReportMapper target;

[SetUp]
public void TestSetUp()
{
  entityMapperMock = new Mock<IEntityMapper>();
  target = new OverdraftReportMapper(entityMapperMock.Object);
} 
4
Henrik Voldby

Verifyの代わりにCallbackメソッドを使用して、呼び出しをカウントできます。

これは Moqクイックスタートページ で示されているため、次のようになります。

// returning different values on each invocation
var mock = new Mock<IFoo>();
var calls = 0;
mock.Setup(foo => foo.GetCountThing())
    .Returns(() => calls)
    .Callback(() => calls++);
// returns 0 on first invocation, 1 on the next, and so on
Console.WriteLine(mock.Object.GetCountThing());
3
Ed Harling

使用しているMockのバージョンによって異なりますが、これができることは確かです

someMockObject.ResetCalls();
3
AustinTX

1つのテストで2つのことを確認しているため、これは確かに単体テストの悪用です。テストからObjectUnderTestの初期化を取り除いて、一般的なセットアップ方法にすれば、あなたの人生はずっと楽になります。その後、テストはより読みやすくなり、相互に独立します。

実動コードよりも、テストコードを読みやすく分離するために最適化する必要があります。システムの動作の1つの側面のテストは、他の側面に影響を与えるべきではありません。モックオブジェクトをリセットするよりも、一般的なコードをセットアップメソッドにリファクタリングする方が、はるかに簡単です。

ObjectUnderTest _objectUnderTest;

[Setup] //Gets run before each test
public void Setup() {
    var foo = new Mock<IFoo>(); //moq by default creates loose mocks
    _objectUnderTest = new ObjectUnderTest(foo.Object);
}
[Test]
public void DoStuffToPushIntoState1ShouldCallFizz() {
    _objectUnderTest.DoStuffToPushIntoState1(); // this is various lines of code and setup

    foo.Verify(x => x.Fizz());
}
[Test]
public void DoStuffToPushIntoState2ShouldntCallFizz() {
{
    objectUnderTest.DoStuffToPushIntoState2(); // more lines of code
    foo.Verify(x => x.Fizz(), Times.Never());
}
2
Igor Zevaka

@stackunderflowによる回答への追加(笑、ニースニック:))

Moqの以降のバージョンでは、Moq.MockExtensions.ResetCalls()は廃止されたとマークされていました。代わりにmock.Invocations.Clear()を使用する必要があります。

foo.Invocations.Clear();
2
CodeFuller

次のアプローチは私にとってはうまくいきます(Moq.Sequenceを使用)

    public void Justification()
    {
        var foo = new Mock<IFoo>(MockBehavior.Loose);
        foo.Setup(x => x.Fizz());

        var objectUnderTest = new ObjectUnderTest(foo.Object);

        objectUnderTest.DoStuffToPushIntoState1(); // this is various lines of code and setup

        foo.Verify(x => x.Fizz());

        // Some cool stuff

        using (Sequence.Create())
        {
            foo.Setup(x => x.Fizz()).InSequence(Times.Never())
            objectUnderTest.DoStuffToPushIntoState2(); // more lines of code
        }
    }

うまくいったかどうか教えてください

0
Artiom