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()
);
_
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 });
すぐに使える別のオプションは、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;
}
}
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
});
_
入力が実際にusername
とpassword
である場合、両方のSetup()
呼び出しは修飾されますが、ルールのために、そして他の入力がある場合、後者の呼び出しが勝ちます。最初のものが一致して適用されます。