web-dev-qa-db-ja.com

Moqを使用して異なるパラメーターに対してメソッドを2回設定する方法

Moqを使用してメソッドを2回セットアップしたいのですが、最後のメソッドが前のメソッドをオーバーライドしているようです。これが私の初期設定です。

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

これは正常に機能しますが、ユーザー名またはパスワードが上記のusernameおよびpassword変数と異なる場合、new ValidUserContext()を返します。そのために、別のセットアップを追加しましたが、今回は上記のセットアップをオーバーライドし、常に適用します。

_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()
);
_
38
tugberk

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 });
51
k.m

すぐに使える別のオプションは、Return <>バージョンを使用して、パラメーターに応じて異なるValidUserContextsを返すことです。上記の答えよりも優れているのではなく、別の選択肢です。

関数GetUserContext(string、string)の結果を返すようにValidateUser()を設定し、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;

    }
}
3
AlanT

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

必要なのは、2つのSetup()呼び出しのorderを切り替えるだけです。

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

入力が実際にusernamepasswordである場合、両方のSetup()呼び出しは修飾されますが、ルールのために、そして他の入力がある場合、後者の呼び出しが勝ちます。最初のものが一致して適用されます。

2
watashiSHUN