Ожидать несколько вызовов метода

Как я могу сказать 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>, Вероятно, есть более подходящие названия для методов и / или причин, по которым это вообще плохая идея.

Я надеюсь, что это будет полезно для вас или кого-то!

Другие вопросы по тегам