it-swarm.com.de

Unterschiedliche Rückgabewerte beim ersten und zweiten Mal mit Moq

Ich habe einen Test wie diesen:

    [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 wird in meinem Dashboardpathresolver zweimal ausgeführt. Wie kann ich Moq anweisen, beim ersten Mal null und beim zweiten pageModel.Ojbect zurückzugeben?

215
Marcus

Das Hinzufügen eines Rückrufs hat bei mir nicht funktioniert, ich habe stattdessen diesen Ansatz gewählt http://haacked.com/archive/2009/09/29/moq-sequences.aspx und es ergab sich ein Test wie Dies:

    [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
Marcus

Mit der neuesten Version von Moq (4.2.1312.1622) können Sie eine Sequenz von Ereignissen mit SetupSequence einrichten. Hier ist ein Beispiel:

_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);

Das Anrufen von connect ist nur beim dritten und fünften Versuch erfolgreich, andernfalls wird eine Ausnahme ausgelöst.

Für Ihr Beispiel wäre es also nur so etwas wie:

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

Die vorhandenen Antworten sind großartig, aber ich dachte, ich würde meine Alternative verwenden, die nur System.Collections.Generic.Queue Verwendet und keine besonderen Kenntnisse des spöttischen Frameworks erfordert - da ich keine hatte, als ich es schrieb! :)

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

Dann...

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

Sie können beim Einrichten Ihres Scheinobjekts einen Rückruf verwenden. Schauen Sie sich das Beispiel aus dem Moq-Wiki an ( 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());

Ihr Setup könnte folgendermaßen aussehen:

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

Jetzt können Sie SetupSequence verwenden. Siehe diesen Beitrag: http://codecontracts.info/2011/07/28/moq-setupsequence-is-great-for-mocking/

19
ilmatte

Die akzeptierte Antwort sowie die SetupSequence-Antwort behandeln die Rückgabe von Konstanten.

Returns() hat einige nützliche Überladungen, bei denen Sie einen Wert zurückgeben können, der auf den Parametern basiert, die an die verspottete Methode gesendet wurden. Basierend auf der Lösung in der akzeptierten Antwort ist hier eine andere Erweiterungsmethode für diese Überladungen.

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));
    }
}

Leider müssen Sie für die Verwendung der Methode einige Vorlagenparameter angeben, aber das Ergebnis ist immer noch gut lesbar.

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,
        });

Erstellen Sie Überladungen für die Erweiterungsmethode mit mehreren Parametern (T2, T3, etc) wenn nötig.

3
Torbjörn Kalin

Erreicht hier für die gleiche Art von Problem mit leicht unterschiedlichen Anforderungen.
Ich muss nterschiedliche Rückgabewerte von mock basierend auf unterschiedlichen Eingabewerten und eine Lösung finden, die meiner Meinung nach besser lesbar ist, da sie die deklarative Syntax von Moq (linq to Mocks) verwendet.

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
2
Saravanan