95

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.

Ash
  • 5,786
  • 5
  • 22
  • 42
Nicholas Murray
  • 13,305
  • 14
  • 65
  • 84

4 Answers4

67

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";
}
Nicholas Murray
  • 13,305
  • 14
  • 65
  • 84
Ufuk Hacıoğulları
  • 37,978
  • 12
  • 114
  • 156
  • 1
    Will try this shortly, was looking at this video http://thethoughtfulcoder.com/blog/52/Moq-Use-Setup-arguments-parameters-in-the-Returns-of-a-mocked-function while you added your answer which does something similar – Nicholas Murray Feb 03 '12 at 14:44
  • 1
    the code above will not compile it complains about Security.MembershipUser does not contain a reference for returns and also about User containing no definition for ProviderUserKey – Nicholas Murray Feb 03 '12 at 15:15
  • You should reference the assembly contaning Security.MembershipUser I guess. Or you can inject a dependency for creating users for you in your MembershipService – Ufuk Hacıoğulları Feb 03 '12 at 15:19
  • hmmm - it doesn't complain about that when I am trying my way - (which is not working as desited of course) – Nicholas Murray Feb 03 '12 at 15:23
  • 1
    I guess you can write another function to create a MembershipUser mock but it's seriously getting out of hand. I updated the code. – Ufuk Hacıoğulları Feb 03 '12 at 15:37
  • 1
    Then you should consider changing your design. Depending on abstractions rather than concrete classes, refactoring your public interface, making your objects have a single responsibilty. – Ufuk Hacıoğulları Feb 03 '12 at 15:44
  • But I am???? var mockMembershipService = new Mock(); I mocked it so there would be no dependencies in the controller. – Nicholas Murray Feb 03 '12 at 15:46
  • You may provide a factory for creating MembersipUser objects. With that you can isolate code for this test. – Ufuk Hacıoğulları Feb 03 '12 at 18:52
  • You could do the same but use `yield` to output a specific sequence in GetProperty method. – Rebecca Dec 13 '13 at 08:15
58

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 */);
cadrell0
  • 17,109
  • 5
  • 51
  • 69
18

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>(...).

Bartosz
  • 3,318
  • 21
  • 31
  • I should get to try this out in about 2 hours. – Nicholas Murray Feb 03 '12 at 11:26
  • Ah well, I think it's because we're setting up a property here (`ProviderUserKey`), while the argument we're trying to act upon comes from `GetUser(...)`. Can't check proper solution right now, but if you follow Ufuk advices, it should be ok... – Bartosz Feb 03 '12 at 21:00
  • I'll give it a go over the weekend - thanks for your help! Not as straight forward as I first thought. – Nicholas Murray Feb 03 '12 at 21:16
  • @mattumotu: That's not what I experience, Moq just has a strange execution policy. Later added Setup calls are evaluated first (or always all are evaluated and last wins). It's just the opposite of what one would expect: Usually the first match is returned and the rest is ignored. – Christoph Nov 21 '19 at 13:00
10

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");
Pure.Krome
  • 84,693
  • 113
  • 396
  • 647
Jaider
  • 14,268
  • 5
  • 75
  • 82
  • 3
    Now with more experience in Unit Tests, I'm not recommend this kind of approach. We should avoid putting logic within the Unit Tests. Another option is creating separated unit tests: one for **Joe** and other for **Tracy** – Jaider Nov 29 '16 at 20:49