MockRepository はさておき、必要Mock
な機能を提供するために を継承するクラスを作成しました。まず、使用法 (XUnit 構文):
[Fact]
public void SomeTest()
{
var mock = new Mock2<IDependency>();
var sut = new Sut(mock.Object);
mock.SetupAndExpect(d => d.DoSomething(It.IsAny<string>(), It.IsAny<long>()),Times.Once).Returns(3);
mock.SetupAndExpect(d => d.DoSomethingNoReturn(It.IsAny<string>()), Times.Once);
mock.SetupAndExpect(d => d.DoSomethingNoReturn(It.IsAny<string>()), Times.Once);
sut.CallDoSomething();
mock.VerifyAllExpectations();
}
このメソッドは、渡されるSetupAndExpect
allow の代替です。はと同等です。必要に応じて、これらの名前をいじることができます。Setup
Times
VerifyAllExpectations
VerifyAll
このMock2
クラスはexpression
、times
渡された と を保存してSetupAndExpect
、後で使用できるようにしVerifyAllExpectations
ます。
コードを示して解決策Mock2
について話す前にMockRepository
、冗長性について説明します。式はすべて、モック化された型に対してジェネリックな型を持っているため、戻り値のないモック化されたメソッドに対して動作させるのはかなり簡単でした。ただし、戻り値を持つメソッドの場合、呼び出される基礎となる検証は ですMock.Verify<TResult>(...)
。正しく閉じられたメソッドにバインドできるようにするために、VerifyAllExpectations
リフレクションを使用することになりました。Mock 自体を変更してこの機能を組み込むことで、ハックの少ないソリューションが可能になると確信しています。
さて、リポジトリに戻ります。私の考えでは、Mock2
から継承するのではなくMock
、 のインスタンスをMock
コンストラクターのパラメーターとして受け取り、それを使用して と を呼び出すSetup
ように変更することを考えていますVerify
。次に、元のインスタンスを呼び出し、作成されたインスタンスをインスタンスのコンストラクターに渡し、それを返す新しい拡張メソッドをMockRepository
( Create2
??)に記述できます。MockRepository.Create
Mock
Mock2
これに対する最終的な代替手段は、拡張メソッドとしてSetupAndExpect
とを に追加することです。ただし、期待情報の保存は、おそらく何らかの静的な状態にする必要があり、クリーンアップの問題に直面するでしょう。VerifyAllExpectations
Mock
Mock2
コードは次のとおりです。
public class Mock2<T> : Mock<T> where T:class
{
private readonly Dictionary<Type, List<Tuple<Expression,Func<Times>>>> _resultTypeKeyedVerifications =
new Dictionary<Type, List<Tuple<Expression, Func<Times>>>>();
private readonly List<Tuple<Expression<Action<T>>, Func<Times>>> _noReturnTypeVerifications =
new List<Tuple<Expression<Action<T>>, Func<Times>>>();
public ISetup<T, TResult> SetupAndExpect<TResult>(Expression<Func<T, TResult>> expression, Func<Times> times)
{
// Store the expression for verifying in VerifyAllExpectations
var verificationsForType = GetVerificationsForType(typeof(TResult));
verificationsForType.Add(new Tuple<Expression, Func<Times>>(expression, times));
// Continue with normal setup
return Setup(expression);
}
public ISetup<T> SetupAndExpect(Expression<Action<T>> expression, Func<Times> times)
{
_noReturnTypeVerifications.Add(new Tuple<Expression<Action<T>>, Func<Times>>(expression, times));
return Setup(expression);
}
private List<Tuple<Expression, Func<Times>>> GetVerificationsForType(Type type)
{
// Simply gets a list of verification info for a particular return type,
// creating it and putting it in the dictionary if it doesn't exist.
if (!_resultTypeKeyedVerifications.ContainsKey(type))
{
var verificationsForType = new List<Tuple<Expression, Func<Times>>>();
_resultTypeKeyedVerifications.Add(type, verificationsForType);
}
return _resultTypeKeyedVerifications[type];
}
/// <summary>
/// Use this instead of VerifyAll for setups perfomed using SetupAndRespect
/// </summary>
public void VerifyAllExpectations()
{
VerifyAllWithoutReturnType();
VerifyAllWithReturnType();
}
private void VerifyAllWithoutReturnType()
{
foreach (var noReturnTypeVerification in _noReturnTypeVerifications)
{
var expression = noReturnTypeVerification.Item1;
var times = noReturnTypeVerification.Item2;
Verify(expression, times);
}
}
private void VerifyAllWithReturnType()
{
foreach (var typeAndVerifications in _resultTypeKeyedVerifications)
{
var returnType = typeAndVerifications.Key;
var verifications = typeAndVerifications.Value;
foreach (var verification in verifications)
{
var expression = verification.Item1;
var times = verification.Item2;
// Use reflection to find the Verify method that takes an Expression of Func of T, TResult
var verifyFuncMethod = GetType()
.GetMethods(BindingFlags.Instance | BindingFlags.Public)
.Single(IsVerifyMethodForReturnTypeAndFuncOfTimes)
.MakeGenericMethod(returnType);
// Equivalent to Verify(expression, times)
verifyFuncMethod.Invoke(this, new object[] {expression, times});
}
}
}
private static bool IsVerifyMethodForReturnTypeAndFuncOfTimes(MethodInfo m)
{
if (m.Name != "Verify") return false;
// Look for the single overload with two funcs, which is the one we want
// as we're looking at verifications for functions, not actions, and the
// overload we're looking for takes a Func<Times> as the second parameter
var parameters = m.GetParameters();
return parameters.Length == 2
&& parameters[0] // expression
.ParameterType // Expression
.GenericTypeArguments[0] // Func
.Name == "Func`2"
&& parameters[1] // times
.ParameterType // Func
.Name == "Func`1";
}
}
最終警告: これは軽くテストされているだけで、同時にテストされていません。Verify
のオーバーロードに相当するものはありませTimes
んFunc<Times>
。メソッドの名前や、そもそもこれが一般的に悪い考えである理由には、おそらくいくつかのより良い名前があります。
それがあなたや誰かに役立つことを願っています!