How to set up a method twice for different parameters with Moq

C#.NetUnit TestingMoq

C# Problem Overview


I would like to set up a method with Moq twice but it seems that the last one overrides the previous ones. Here's my initial setup:

string username = "foo";
string password = "bar";

var principal = new GenericPrincipal(
    new GenericIdentity(username),
    new[] { "Admin" });

var membershipServiceMock = new Mock<IMembershipService>();
membershipServiceMock.Setup(ms =>
    ms.ValidateUser(username, password)
).Returns(new ValidUserContext { 
    Principal = principal
});

This works out fine but I want this to return new ValidUserContext() if the username or password is different to the username and password variables as above. To do that, I added another setup but this time it overrides the above one and always applies it:

membershipServiceMock.Setup(ms =>
    ms.ValidateUser(It.IsAny<string>(), It.IsAny<string>())
).Returns(
    new ValidUserContext()
);

What is the most elegant way of handling this type of situation with Moq?

Edit

I solved the problem with the below approach but I guess there is a better way of handling this:

var membershipServiceMock = new Mock<IMembershipService>();
membershipServiceMock.Setup(ms =>
    ms.ValidateUser(It.IsAny<string>(), It.IsAny<string>())
).Returns<string, string>((u, p) => 
    (u == username && p == password) ?
    new ValidUserContext { 
        Principal = principal
    }
    : new ValidUserContext()
);

C# Solutions


Solution 1 - C#

Moq supports this out of box with argument constraints:

mock.Setup(ms => ms.ValidateUser(
        It.Is<string>(u => u == username), It.Is<string>(p => p == password))
    .Returns(new ValidUserContext { Principal = principal });
mock.Setup(ms => ms.ValidateUser(
        It.Is<string>(u => u != username), It.Is<string>(p => p != password))
    .Returns(new ValidUserContext());

Catch-all It.IsAny also works, but the order is important:

// general constraint first so that it doesn't overwrite more specific ones
mock.Setup(ms => ms.ValidateUser(
        It.IsAny<string>(), It.IsAny<string>())
    .Returns(new ValidUserContext());
mock.Setup(ms => ms.ValidateUser(
        It.Is<string>(u => u == username), It.Is<string>(p => p == password))
    .Returns(new ValidUserContext { Principal = principal });

Solution 2 - C#

If you look at the function definition for Setup():

// Remarks:
//     If more than one setup is specified for the same method or property, the latest
//     one wins and is the one that will be executed.
public ISetup<T, TResult> Setup<TResult>(Expression<Func<T, TResult>> expression);

All you need to do is switch the order of the two Setup() calls:

membershipServiceMock.Setup(ms =>
    ms.ValidateUser(It.IsAny<string>(), It.IsAny<string>())
).Returns(
    new ValidUserContext()
);
membershipServiceMock.Setup(ms =>
    ms.ValidateUser(username, password)
).Returns(new ValidUserContext { 
    Principal = principal
});

so if the input is indeed username and password, both Setup() calls are qualified but the later one wins because of the rule and when you have any other inputs, only the first one is matched and applied.

Solution 3 - C#

Another out-of-the-box option is to use the Return<> version to return different ValidUserContexts depending upon the parameters. It is not better than the above answer, just another option.

We set up ValidateUser() to return the result of a function GetUserContext(string, string), passing in the username and password with which ValidateUser() was called.

[TestClass]
public class MultipleReturnValues {

    public class ValidUserContext {
        public string Principal { get; set; }
    }

    public interface IMembershipService {
        ValidUserContext ValidateUser(string name, string password);
    }

    [TestMethod]
    public void DifferentPricipals() {

        var mock = new Mock<IMembershipService>();
        mock.Setup(mk => mk.ValidateUser(It.IsAny<string>(), It.IsAny<string>())).Returns<string, string>(GetUserContext);

        var validUserContext = mock.Object.ValidateUser("abc", "cde");

        Assert.IsNull(validUserContext.Principal);


        validUserContext = mock.Object.ValidateUser("foo", "bar");

        Assert.AreEqual(sPrincipal, validUserContext.Principal);


    }

    private static string sPrincipal = "A Principal";
    private static ValidUserContext GetUserContext(string name, string password) {

        var ret = new ValidUserContext();

        if (name == "foo" && password == "bar") {
            ret = new ValidUserContext { Principal = sPrincipal };
        }
        return ret;

    }
}

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestiontugberkView Question on Stackoverflow
Solution 1 - C#k.mView Answer on Stackoverflow
Solution 2 - C#watashiSHUNView Answer on Stackoverflow
Solution 3 - C#AlanTView Answer on Stackoverflow