多个Moq It.Is<string>()匹配参数

96

使用Moq,是否可以有多个匹配参数?

It.Is<string>() 
在这个例子中,我希望 mockMembershipService 根据所提供的 User 返回不同的 ProviderUserKey。
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");

SetUp 默认选择第二个语句,而不是根据各自的价值进行评估。

4个回答

67

这不会让人感到困惑吗?你试图模拟 GetUser 方法,但是你设置了该函数返回值的属性。你还想根据模拟的方法说明返回类型的属性。

以下是一种更清晰的方式:

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

这是创建成员资格模拟的方法:

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

然后编写一个设置该属性的方法:

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

1
马上尝试一下,我正在观看这个视频http://thethoughtfulcoder.com/blog/52/Moq-Use-Setup-arguments-parameters-in-the-Returns-of-a-mocked-function,而你则提供了类似的答案。 - Nicholas Murray
1
上述代码将无法编译,因为它抱怨 Security.MembershipUser 不包含返回引用,同时也抱怨 User 中没有 ProviderUserKey 的定义。 - Nicholas Murray
1
我猜你可以写另一个函数来创建一个MembershipUser模拟,但这样做会让事情变得更加棘手。我已经更新了代码。 - Ufuk Hacıoğulları
1
那么你应该考虑改变你的设计。不要依赖具体类而是抽象类,重构你的公共接口,让你的对象只有一个职责。 - Ufuk Hacıoğulları
你可以使用yieldGetProperty方法中输出特定的序列。 - Rebecca
显示剩余4条评论

58

如果你想将输入限制为只有"Joe"和"Tracy",你可以在It.Is<T>()中指定多个条件。像这样:

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

并不是我想要限制,我只是想评估输入并返回所需的输出 :-) - Nicholas Murray
无论这是否在正确的位置,它都帮助了我,谢谢 @cadrell0。 - Jacob McKay

18

连续的设置将使之前的设置失效。

您可以在返回回调函数中使用您的参数:

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

如果你认为确保传递的参数很重要的话,你还需要使用It.Is<string>(...)而不是It.IsAny<string>(...)


大约两个小时后,我应该能够试一下这个。 - Nicholas Murray
嗯,我认为这是因为我们在这里设置了一个属性(ProviderUserKey),而我们试图操作的参数来自于 GetUser(...)。现在无法检查正确的解决方案,但如果你遵循 Ufuk 的建议,应该没问题... - Bartosz
我会在周末试一试 - 感谢你的帮助!这并不像我最初想象的那么简单。 - Nicholas Murray
@mattumotu:这不是我的经验,Moq只有一个奇怪的执行策略。稍后添加的Setup调用首先被评估(或者总是所有都被评估,最后一个获胜)。这正好与人们期望的相反:通常会返回第一个匹配项并忽略其余部分。 - Christoph

11

请查看Moq入门 > 匹配参数文档:

// 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");

3
由于我经验的积累,我不建议采用这种方法。我们应该避免在单元测试中放置逻辑代码。另一个选择是创建分离的单元测试:一个针对Joe,另一个针对Tracy。 - Jaider

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接