Moqで最初と2回目に異なる戻り値


262

私はこのようなテストをしています:

    [TestCase("~/page/myaction")]
    public void Page_With_Custom_Action(string path) {
        // Arrange
        var pathData = new Mock<IPathData>();
        var pageModel = new Mock<IPageModel>();
        var repository = new Mock<IPageRepository>();
        var mapper = new Mock<IControllerMapper>();
        var container = new Mock<IContainer>();

        container.Setup(x => x.GetInstance<IPageRepository>()).Returns(repository.Object);

        repository.Setup(x => x.GetPageByUrl<IPageModel>(path)).Returns(() => pageModel.Object);

        pathData.Setup(x => x.Action).Returns("myaction");
        pathData.Setup(x => x.Controller).Returns("page");

        var resolver = new DashboardPathResolver(pathData.Object, repository.Object, mapper.Object, container.Object);

        // Act
        var data = resolver.ResolvePath(path);

        // Assert
        Assert.NotNull(data);
        Assert.AreEqual("myaction", data.Action);
        Assert.AreEqual("page", data.Controller);
    }

GetPageByUrlで2回実行されますがDashboardPathResolver、Moqにnull最初とpageModel.Object2番目に戻るように指示するにはどうすればよいですか?

回答:


454

最新バージョンのMoq(4.2.1312.1622)では、SetupSequenceを使用してイベントのシーケンスをセットアップできます。次に例を示します。

_mockClient.SetupSequence(m => m.Connect(It.IsAny<String>(), It.IsAny<int>(), It.IsAny<int>()))
        .Throws(new SocketException())
        .Throws(new SocketException())
        .Returns(true)
        .Throws(new SocketException())
        .Returns(true);

接続の呼び出しは、3回目と5回目の試行でのみ成功します。それ以外の場合は、例外がスローされます。

したがって、あなたの例では、それは次のようなものになります:

repository.SetupSequence(x => x.GetPageByUrl<IPageModel>(virtualUrl))
.Returns(null)
.Returns(pageModel.Object);

2
いい答えですが、唯一の制限は "SetupSequence"が保護されたメンバーでは機能しないことです。
Chasefornone 2016年

7
残念ながら、SetupSequence()では動作しませんCallback()。もしそうなら、モックされたメソッドへの呼び出しを「ステートマシン」の方法で検証できます。
urig 2017

@stackunderflow SetupSequenceは2つの呼び出しに対してのみ機能しますが、3つ以上の呼び出しが必要な場合はどうすればよいですか?
TanvirArjel

@TanvirArjel、意味がわからない... SetupSequence任意の数の呼び出しに使用できます。最初の例では、5つの呼び出しのシーケンスを返します。
stackunderflow

@stackunderflowすみません!これは私の誤解でした!はい!正解です。
TanvirArjel

115

既存の答えは素晴らしいSystem.Collections.Generic.Queueですが、モックフレームワークの特別な知識を必要とせず、使用するだけの代替案を投入すると思いました。私が書いたときに何も持っていなかったからです。:)

var pageModel = new Mock<IPageModel>();
IPageModel pageModelNull = null;
var pageModels = new Queue<IPageModel>();
pageModels.Enqueue(pageModelNull);
pageModels.Enqueue(pageModel.Object);

その後...

repository.Setup(x => x.GetPageByUrl<IPageModel>(path)).Returns(pageModels.Dequeue);

ありがとう。pageModel.Objectの代わりにpageModelモックをエンキューしていたタイプミスを修正したので、ビルドもできるはずです!:)
mo。

3
答えは正しいExceptionですが、できる限りスローしたい場合、これは機能しないことに注意してくださいEnqueue。しかし、SetupSequence動作します(たとえば、@ stackunderflowからの回答を参照してください)。
ハルバード2014年

4
デキューには委任されたメソッドを使用する必要があります。サンプルの記述方法では、デキューはセットアップ時に評価されるため、常にキューの最初の項目が繰り返し返されます。
Jason Coyne 2014

7
それはデリゲートです。コードがのDequeue()代わりに含まれている場合Dequeue、あなたは正しいでしょう。
mo。

31

コールバックが私のために動作しませんでした追加、私は、このアプローチが代わりに使用http://haacked.com/archive/2009/09/29/moq-sequences.aspxと私はこのようなテストになってしまいました。

    [TestCase("~/page/myaction")]
    [TestCase("~/page/myaction/")]
    public void Page_With_Custom_Action(string virtualUrl) {

        // Arrange
        var pathData = new Mock<IPathData>();
        var pageModel = new Mock<IPageModel>();
        var repository = new Mock<IPageRepository>();
        var mapper = new Mock<IControllerMapper>();
        var container = new Mock<IContainer>();

        container.Setup(x => x.GetInstance<IPageRepository>()).Returns(repository.Object);
        repository.Setup(x => x.GetPageByUrl<IPageModel>(virtualUrl)).ReturnsInOrder(null, pageModel.Object);

        pathData.Setup(x => x.Action).Returns("myaction");
        pathData.Setup(x => x.Controller).Returns("page");

        var resolver = new DashboardPathResolver(pathData.Object, repository.Object, mapper.Object, container.Object);

        // Act
        var data = resolver.ResolvePath(virtualUrl);

        // Assert
        Assert.NotNull(data);
        Assert.AreEqual("myaction", data.Action);
        Assert.AreEqual("page", data.Controller);
    }

29

モックオブジェクトを設定するときにコールバックを使用できます。Moq Wiki(http://code.google.com/p/moq/wiki/QuickStart)の例をご覧ください

// 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());

セットアップは次のようになります。

var pageObject = pageModel.Object;
repository.Setup(x => x.GetPageByUrl<IPageModel>(path)).Returns(() => pageObject).Callback(() =>
            {
                // assign new value for second call
                pageObject = new PageModel();
            });

1
これを行うと、どちらもnullになります。var pageModel = new Mock <IPageModel>(); IPageModelモデル= null; repository.Setup(x => x.GetPageByUrl <IPageModel>(path))。Returns(()=> model).Callback(()=> {model = pageModel.Object;});
marcus

GetPageByUrlはresolver.ResolvePathメソッド内で2回呼び出されますか?
Dan

ResolvePathには以下のコードが含まれていますが、両方ともnullのままです。var foo = _repository.GetPageByUrl <IPageModel>(virtualUrl); var foo2 = _repository.GetPageByUrl <IPageModel>(virtualUrl);
marcus

2
コールバックアプローチが機能しないことを確認しました(以前のMoqバージョンで試した場合でも)。別の可能なアプローチ-あなたのテストに依存します-をSetup()もう一度呼び出すだけでReturn()、別の値になります。
ケントブーガート2013


4

わずかに異なる要件を持つ同じ種類の問題についてここに達しました。さまざまな入力値に基づいてモックからさまざまな戻り値
を取得する必要があります。また、Moqの宣言構文(linqからMocks)を使用しているため、IMOがより読みやすいソリューションを見つけました。

public interface IDataAccess
{
   DbValue GetFromDb(int accountId);  
}

var dataAccessMock = Mock.Of<IDataAccess>
(da => da.GetFromDb(It.Is<int>(acctId => acctId == 0)) == new Account { AccountStatus = AccountStatus.None }
&& da.GetFromDb(It.Is<int>(acctId => acctId == 1)) == new DbValue { AccountStatus = AccountStatus.InActive }
&& da.GetFromDb(It.Is<int>(acctId => acctId == 2)) == new DbValue { AccountStatus = AccountStatus.Deleted });

var result1 = dataAccessMock.GetFromDb(0); // returns DbValue of "None" AccountStatus
var result2 = dataAccessMock.GetFromDb(1); // returns DbValue of "InActive"   AccountStatus
var result3 = dataAccessMock.GetFromDb(2); // returns DbValue of "Deleted" AccountStatus

私(2019年のMoq 4.13.0はこちら)では、-lambdaは必要da.GetFromDb(0) == new Account { ..None.. && da.GetFromDb(1) == new Account { InActive } && ...なく、より短くても機能しましたIt.Is
ojdo

3

受け入れ答えだけでなく、SetupSequenceの答えは、ハンドルは定数を返します。

Returns()には、モックされたメソッドに送信されたパラメーターに基づいて値を返すことができるいくつかの便利なオーバーロードがあります。受け入れられた回答で与えられた解決策に基づいて、ここにそれらのオーバーロードの別の拡張方法があります。

public static class MoqExtensions
{
    public static IReturnsResult<TMock> ReturnsInOrder<TMock, TResult, T1>(this ISetup<TMock, TResult> setup, params Func<T1, TResult>[] valueFunctions)
        where TMock : class
    {
        var queue = new Queue<Func<T1, TResult>>(valueFunctions);
        return setup.Returns<T1>(arg => queue.Dequeue()(arg));
    }
}

残念ながら、このメソッドを使用するには、いくつかのテンプレートパラメーターを指定する必要がありますが、結果は非常に読みやすくなっています。

repository
    .Setup(x => x.GetPageByUrl<IPageModel>(path))
    .ReturnsInOrder(new Func<string, IPageModel>[]
        {
            p => null, // Here, the return value can depend on the path parameter
            p => pageModel.Object,
        });

複数のパラメータ(と拡張メソッドのためのオーバーロードを作成しT2T3必要に応じて、など)。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.