Different return values the first and second time with Moq

115,602

Solution 1

Adding a callback did not work for me, I used this approach instead http://haacked.com/archive/2009/09/29/moq-sequences.aspx and I ended up with a test like this:

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

Solution 2

With the latest version of Moq(4.2.1312.1622), you can setup a sequence of events using SetupSequence. Here's an example:

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

Calling connect will only be successful on the third and fifth attempt otherwise an exception will be thrown.

So for your example it would just be something like:

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

Solution 3

The existing answers are great, but I thought I'd throw in my alternative which just uses System.Collections.Generic.Queue and doesn't require any special knowledge of the mocking framework - since I didn't have any when I wrote it! :)

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

Then...

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

Solution 4

Now you can use SetupSequence. See this post.

var mock = new Mock<IFoo>();
mock.SetupSequence(f => f.GetCount())
    .Returns(3)  // will be returned on 1st invocation
    .Returns(2)  // will be returned on 2nd invocation
    .Returns(1)  // will be returned on 3rd invocation
    .Returns(0)  // will be returned on 4th invocation
    .Throws(new InvalidOperationException());  // will be thrown on 5th invocation

Solution 5

You can use a callback when setting up your mock object. Take a look at the example from the Moq Wiki (https://github.com/Moq/moq4/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());

Your setup might look like this:

var pageObject = pageModel.Object;
repository.Setup(x => x.GetPageByUrl<IPageModel>(path)).Returns(() => pageObject).Callback(() =>
            {
                // assign new value for second call
                pageObject = new PageModel();
            });
Share:
115,602
marcus
Author by

marcus

Updated on February 18, 2022

Comments

  • marcus
    marcus about 2 years

    I have a test like this:

        [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 runs twice in my DashboardPathResolver, how can I tell Moq to return null the first time and pageModel.Object the second?