Using Moq to verify calls are made in the correct order

后端 未结 8 1580
青春惊慌失措
青春惊慌失措 2020-12-01 10:00

I need to test the following method:

CreateOutput(IWriter writer)
{
    writer.Write(type);
    writer.Write(id);
    writer.Write(sender);

    // many more         


        
相关标签:
8条回答
  • 2020-12-01 10:58

    Recently, I put together two features for Moq: VerifyInSequence() and VerifyNotInSequence(). They work even with Loose Mocks. However, these are only available in a moq repository fork:

    https://github.com/grzesiek-galezowski/moq4

    and await more comments and testing before deciding on whether they can be included in official moq releaase. However, nothing prevents you from downloading the source as ZIP, building it into a dll and giving it a try. Using these features, the sequence verification you need could be written as such:

    var mockWriter = new Mock<IWriter>() { CallSequence = new LooseSequence() };
    
    //perform the necessary calls
    
    mockWriter.VerifyInSequence(x => x.Write(expectedType));
    mockWriter.VerifyInSequence(x => x.Write(expectedId));
    mockWriter.VerifyInSequence(x => x.Write(expectedSender));
    

    (note that you can use two other sequences, depending on your needs. Loose sequence will allow any calls between the ones you want to verify. StrictSequence will not allow this and StrictAnytimeSequence is like StrictSequence (no method calls between verified calls), but allows the sequence to be preceeded by any number of arbitrary calls.

    If you decide to give this experimental feature a try, please comment with your thoughts on: https://github.com/Moq/moq4/issues/21

    Thanks!

    0 讨论(0)
  • 2020-12-01 10:59

    I am late to this party but I wanted to share a solution that worked for me since it seems as though all of the referenced solutions did not work with verifying the same method call (with the same arguments) multiple times in order. In addition the referenced bug, Moq Issue #478 was closed without a solution.

    The solution presented utilizes the MockObject.Invocations list to determine order and sameness.

    public static void VerifyInvocations<T>(this Mock<T> mock, params Expression<Action<T>>[] expressions) where T : class
    {
        Assert.AreEqual(mock.Invocations.Count, expressions.Length,
            $"Number of invocations did not match expected expressions! Actual invocations: {Environment.NewLine}" +
            $"{string.Join(Environment.NewLine, mock.Invocations.Select(i => i.Method.Name))}");
    
        for (int c = 0; c < mock.Invocations.Count; c++)
        {
            IInvocation expected = mock.Invocations[c];
            MethodCallExpression actual = expressions[c].Body as MethodCallExpression;
    
            // Verify that the same methods were invoked
            Assert.AreEqual(expected.Method, actual.Method, $"Did not invoke the expected method at call {c + 1}!");
    
            // Verify that the method was invoked with the correct arguments
            CollectionAssert.AreEqual(expected.Arguments.ToList(),
                actual.Arguments
                    .Select(arg =>
                    {
                        // Expressions treat the Argument property as an Expression, do this to invoke the getter and get the actual value.
                        UnaryExpression objectMember = Expression.Convert(arg, typeof(object));
                        Expression<Func<object>> getterLambda = Expression.Lambda<Func<object>>(objectMember);
                        Func<object> objectValueGetter = getterLambda.Compile();
                        return objectValueGetter();
                    })
                    .ToList(),
                $"Did not invoke step {c + 1} method '{expected.Method.Name}' with the correct arguments! ");
        }
    }
    
    0 讨论(0)
提交回复
热议问题