C# 如何使用 Moq 为不同的参数设置两次方法

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/12871897/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-10 00:46:05  来源:igfitidea点击:

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

c#.netunit-testingmoq

提问by tugberk

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:

我想用 Moq 设置一个方法两次,但似乎最后一个覆盖了以前的方法。这是我的初始设置:

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 usernameand passwordvariables as above. To do that, I added another setup but this time it overrides the above one and always applies it:

这很好用,但new ValidUserContext()如果用户名或密码与上面的usernamepassword变量不同,我希望它返回。为此,我添加了另一个设置,但这次它覆盖了上面的设置并始终应用它:

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?

使用 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()
);

采纳答案by k.m

Moq supports this out of box with argument constraints:

Moq 支持这个开箱即用的参数约束:

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-allIt.IsAnyalso works, but the order is important:

Catch-allIt.IsAny也有效,但顺序很重要:

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

回答by AlanT

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.

另一个开箱即用的选项是使用 Return<> 版本根据参数返回不同的 ValidUserContexts。它并不比上面的答案更好,只是另一种选择。

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.

我们设置 ValidateUser() 以返回函数 GetUserContext(string, string) 的结果,传入调用 ValidateUser() 的用户名和密码。

[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;

    }
}

回答by watashiSHUN

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

如果您查看 的函数定义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 orderof the two Setup()calls:

您需要做的就是切换两个调用的顺序Setup()

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 usernameand 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.

因此,如果输入确实是usernameand password,则两个Setup()调用都符合条件,但由于规则而后一个调用获胜,并且当您有任何其他输入时,仅匹配并应用第一个调用。