Ожидать несколько вызовов метода
Как я могу сказать Moq ожидать нескольких звонков, чтобы я все еще мог использовать MockRepository
в VerifyAll
как ниже?
[TestFixture]
public class TestClass
{
[SetUp]
public void SetUp()
{
_mockRepository = new MockRepository(MockBehavior.Strict);
_mockThing = _mockRepository.Create<IThing>();
_sut = new Sut(_mockThing.Object);
}
[TearDown]
public void TearDown()
{
_mockRepository.VerifyAll();
}
private Mock<IThing> _mockThing;
private MockRepository _mockRepository;
[Test]
public void TestManyCalls(Cell cell)
{
_mockThing.SetUp(x => x.DoSomething()).Return(new DoneStatus());
}
}
Я знаю, что вы можете сделать это во время проверки, но тогда мне придется проверять все самостоятельно. Можно ли сказать, чего ожидать, а не проверять это после события?
Что-то похожее на:
_mockThing.SetUp(x => x.DoSomething()).Return(new DoneStatus()).Times(20);
По сути, я хотел бы установить все свои ожидания в начале теста, а не в нескольких местах.
1 ответ
Отложив 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
метод является заменой 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>
, Вероятно, есть более подходящие названия для методов и / или причин, по которым это вообще плохая идея.
Я надеюсь, что это будет полезно для вас или кого-то!