Multiple Moq It.Is<string>() Matching Arguments

C#Unit TestingMockingMoq

C# Problem Overview


With Moq, is it valid to have more than one Matching Argument?

It.Is<string>() 

In this example I want the mockMembershipService to return a different ProviderUserKey depending on the User supplied.

mockMembershipService.Setup(
    x => x.GetUser(
      It.Is<string>(
        s => s.Contains("Joe")))
   .ProviderUserKey)
.Returns("1234abcd");


mockMembershipService.Setup(
  x => x.GetUser(
    It.Is<string>(
      s => s.Contains("Tracy")))
  .ProviderUserKey)
.Returns("5678efgh");

The SetUp defaults to the second statement rather than evaluating each on its own merits.

C# Solutions


Solution 1 - C#

Isn't it confusing? You are trying to mock GetUser method but you set the Returns for that function's return value's property. You also want to state return type's property based on mocked method.

Here's a way a more clear way:

mockMembershipService.Setup(x => x.GetUser(It.IsAny<string>())
                     .Returns<string>(GetMembershipUser);

Here's a method to create the membership mock:

private MembershipUser GetMembershipUser(string s)
{
    Mock<MembershipUser> user =new Mock<MembershipUser>();
    user.Setup(item => item.ProviderUserKey).Returns(GetProperty(s));
    return user.Object;
}

Then you write a method for setting that property:

private string GetProperty(string s)
{
    if(s.Contains("Joe"))
        return "1234abcd";
    else if(s.Contains("Tracy"))
        return "5678efgh";
}

Solution 2 - C#

If you want to restrict input to just "Joe" and "Tracy", you can specify multiple conditions in It.Is<T>(). Something like

mockMembershipService.Setup(x => x.GetUser(It.Is<String>(s => s.Contains("Joe") 
                                                         || s.Contains("Tracy")))
    .Returns<string>(/* Either Bartosz's or Ufuk's answer */);

Solution 3 - C#

Succesive Setup calls nullify previous setups.

You could use your argument in your return callback:

mockMembershipService.Setup(x => x.GetUser(It.IsAny<string>()).ProviderUserKey).Returns<string>(s =>
{
    if(s.Contains("Joe"))
        return "1234abcd";
    else if(s.Contains("Tracy"))
        return "5678efgh";
});

If it's important to you to assert the argument passed, you also need It.Is<string>(...) instead of It.IsAny<string>(...).

Solution 4 - C#

Please check Introduction to Moq > Matching Arguments documentation:

// matching Func<int>, lazy evaluated
mock.Setup(foo => foo.Add(It.Is<int>(i => i % 2 == 0))).Returns(true); 


// matching ranges
mock.Setup(foo => foo.Add(It.IsInRange<int>(0, 10, Range.Inclusive))).Returns(true); 


// matching regex
mock.Setup(x => x.DoSomething(It.IsRegex("[a-d]+", RegexOptions.IgnoreCase))).Returns("foo");

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
QuestionNicholas MurrayView Question on Stackoverflow
Solution 1 - C#Ufuk HacıoğullarıView Answer on Stackoverflow
Solution 2 - C#cadrell0View Answer on Stackoverflow
Solution 3 - C#BartoszView Answer on Stackoverflow
Solution 4 - C#JaiderView Answer on Stackoverflow