401

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?

harriyott
  • 10,505
  • 10
  • 64
  • 103
marcus
  • 9,616
  • 9
  • 58
  • 108

9 Answers9

631

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);
stackunderflow
  • 10,122
  • 4
  • 20
  • 29
  • 3
    Nice answer, the only limitation is the "SetupSequence" does not work with protected members. – Chasefornone Mar 26 '16 at 08:20
  • 11
    Alas, `SetupSequence()` does not work with `Callback()`. If only it did, one could verify calls to the mocked method in a "state machine" fashion. – urig Aug 15 '17 at 14:36
  • What about `SetupGet` and `SetupSet`? – user3613932 Dec 06 '19 at 03:14
  • @user3613932, can you setup a sequence with `SetupGet` and `SetupSet` where the behavior is different from one call to the next? I think that's what @Marcus was asking for when he asked the question in 2011. – stackunderflow Jan 08 '20 at 19:04
  • 1
    This should be marked as the correct answer. Some of the options presented are valid, but this one is clean and uses the Moq features in the correct way. For the callback, you can use "CallBase" of the ISetupSequentialResult interface. Also, it seems the ReturnInOrder method is no longer supported in most recent versions of Moq. – Fabricio Dec 08 '20 at 23:00
  • what if i need to examine the input variables to the GetPageByUrl() stub? Setup() allows me to do that, SetupSequence() does not. It would be helpful if it did. – StvnBrkdll Mar 02 '21 at 16:15
127

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);
mo.
  • 4,165
  • 3
  • 34
  • 45
  • 3
    The answer is correct, but note that this will not work if you want to throw an `Exception` as you can't `Enqueue` it. But `SetupSequence` will work (see answer from @stackunderflow for example). – Halvard Jun 23 '14 at 07:13
  • 4
    You have to use a delegated method for the Dequeue. The way the sample is written it will always return the first item in the queue repeatedly, because the dequeue is evaluated at time of setup. – Jason Coyne Aug 08 '14 at 15:43
  • 9
    That is a delegate. If the code contained `Dequeue()` instead of just `Dequeue`, you'd be correct. – mo. Aug 11 '14 at 14:21
93

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
dashesy
  • 2,596
  • 3
  • 45
  • 61
ilmatte
  • 1,732
  • 16
  • 12
36

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();
            });
hlovdal
  • 26,565
  • 10
  • 94
  • 165
Dan
  • 17,375
  • 3
  • 36
  • 39
  • 1
    I get null both time when I do this: var pageModel = new Mock(); IPageModel model = null; repository.Setup(x => x.GetPageByUrl(path)).Returns(() => model).Callback(() => { model = pageModel.Object; }); – marcus Sep 02 '11 at 18:37
  • Is GetPageByUrl called twice within the resolver.ResolvePath method? – Dan Sep 02 '11 at 18:42
  • ResolvePath contains the code below but it's still null both times var foo = _repository.GetPageByUrl(virtualUrl); var foo2 = _repository.GetPageByUrl(virtualUrl); – marcus Sep 03 '11 at 08:03
  • 2
    Confirmed that the callback approach does not work (even tried in earlier Moq version). Another possible approach - depending on your test - is to just do the `Setup()` call again, and `Return()` a different value. – Kent Boogaart Feb 01 '13 at 10:29
31

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);
    }
arcain
  • 14,920
  • 6
  • 55
  • 75
marcus
  • 9,616
  • 9
  • 58
  • 108
6

The accepted answer, as well as the SetupSequence answer, handles returning constants.

Returns() has some useful overloads where you can return a value based on the parameters that were sent to the mocked method. Based on the solution given in the accepted answer, here is another extension method for those overloads.

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

Unfortunately, using the method requires you to specify some template parameters, but the result is still quite readable.

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

Create overloads for the extension method with multiple parameters (T2, T3, etc) if needed.

Community
  • 1
  • 1
Torbjörn Kalin
  • 1,976
  • 1
  • 22
  • 31
4

Reached here for the same kind of problem with slightly different requirement.
I need to get different return values from mock based in different input values and found solution which IMO more readable as it uses Moq's declarative syntax (linq to Mocks).

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
Saravanan
  • 920
  • 9
  • 22
  • For me (Moq 4.13.0 from 2019 here), it worked even with the shorter `da.GetFromDb(0) == new Account { ..None.. && da.GetFromDb(1) == new Account { InActive } && ...`, no `It.Is`-lambda required at all. – ojdo Sep 06 '19 at 15:52
1

We can simply declare one variable with int as a datatype. initialize it to zero, and then increase it's value as follows:

int firstTime = 0;
            repository.Setup(_ => _.GetPageByUrl<IPageModel>(path)).Returns(() =>
            {
                if (firstTime == 0)
                {
                    firstTime = 1;
                    return null;
                }
                else if(firstTime == 1)
                {
                    firstTime = 2;
                    return pageModel.Object;
                }
                else
                {
                    return null;
                }
            });
0

In some cases one needs to have the called function return different types of data based on conditions you cannot impose via the function itself. If the function accepts parameters then those can be used as conditionals to get different data.

In my case, I had a webapi call which I need to mock; earlier it was working out fine based on the input parameter, however one fine day, those parameters were converted to request headers. So since I couldn't provide a callback (no function parameters) so came up with another approach as follows

[Earlier one, when API had parameters]

this.mockedMasterAPICalls.Setup(m => m.GetCountries(It.Is<int>(ou => ou == 2), It.Is<int>(lan => lan == 1))).Returns(Task.FromResult(countryResponse));

[New one, when API had headers... The headers were getting injected into another dictionary of the API caller]

   this.mockedMasterAPICalls.Setup(m => m.RequestHeaders).Returns(new Dictionary<string, string>());
            this.mockedMasterAPICalls.Setup(m => m.GetCountries()).Returns(() =>
          {
              if (this.mockedMasterAPICalls.Object.RequestHeaders[GlobalConstants.HeaderOUInstance] == "2")
                  return Task.FromResult(countryResponse);
              else return Task.FromResult(new GetCountryResponse() { Countries = null });
          });

Note the use of the mocked object itself to make any decisions required

NitinSingh
  • 2,029
  • 1
  • 15
  • 33