69

我想用 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
});

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

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

用 Moq 处理这种情况的最优雅的方法是什么?

编辑

我用下面的方法解决了这个问题,但我想有更好的方法来处理这个问题:

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()
);
4

3 回答 3

90

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

包罗万象 It.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 });
于 2012-10-13T10:38:03.683 回答
13

如果您查看以下的函数定义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);

您需要做的就是切换两个调用的顺序: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
});

因此,如果输入确实是usernameand password,那么两个Setup()调用都是合格的,但是后一个调用会因为规则而获胜,并且当您有任何其他输入时,只有第一个调用被匹配和应用。

于 2018-07-03T23:25:33.353 回答
5

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

我们设置 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;

    }
}
于 2012-10-17T08:44:04.880 回答