'Mocking Microsoft.Toolkit.Mvvm.IMessenger

It seems that for some reason, Microsoft has created an interface for it's messenger and then gone and implemented the logic as extension methods on the interface itself.

Unfortunately, I cannot use this beautiful solution: http://agooddayforscience.blogspot.com/2017/08/mocking-extension-methods.html - because IMessenger extensions calls implemented code on Messenger with an internal type as argument.

Why would Microsoft go to such lengths to make unit testing hard? (If you know a good, technical reason for this, please comment with the answer. I am very curious).

I want to unit test the ViewModels, which injects IMessenger. So how do I do this?

My solution is: Wrap IMessenger in a wrapper with an interface and inject that instead.

Is there a simpler/better solution? (I want it to be easy to understand and maintain).



Solution 1:[1]

Wrapping the interface (as others have suggested) is certainly an answer, but I don't like the cognitive overhead of having two interfaces so I came up with this. It does use a bit of reflection, but I couldn't find any other way to get at that object.

Long story short, it manually builds the setup expression against the internal type:

// Compile error, because Unit is internal
mock.Setup(x => x.Send<MyMessage, Unit>(It.IsAny<MyMessage>(), default));

But reflection and expression trees can still give it to us:

private static Type UnitType { get; } = typeof(IMessenger).Assembly
    .GetType("Microsoft.Toolkit.Mvvm.Messaging.Internals.Unit");

public static ISetup<IMessenger, TMessage> SetupMessage<TMessage>(this Mock<IMessenger> messenger,
    Expression<Func<TMessage, bool>>? validation = null)
    where TMessage : class
{
    MethodInfo sendMethod = typeof(IMessenger).GetMethod(nameof(IMessenger.Send))
        .MakeGenericMethod(typeof(TMessage), UnitType);

    ParameterExpression parameter = Expression.Parameter(typeof(IMessenger));

    Expression<Func<TMessage>> message = validation switch
    {
        null => () => It.IsAny<TMessage>(),
        not null => () => It.Is(validation),
    };

    MethodCallExpression methodCall = Expression.Call(
        parameter,
        sendMethod,
        message.Body,
        Expression.Default(UnitType));

    Type funcType = Expression.GetFuncType(typeof(IMessenger), typeof(TMessage));
    Expression lambda = Expression.Lambda(funcType, methodCall, parameter);

    return messenger.Setup((Expression<Func<IMessenger, TMessage>>) lambda);
}

Solution 2:[2]

Moq is incredibly extensible, and provides an extension point for exactly this purpose. You can provide custom type matching logic by creating a type that implements ITypeMatcher. To match the IMessenger.Send signature, for instance:

[TypeMatcher]
public sealed class IsAnyToken : ITypeMatcher, IEquatable<IsAnyToken>
{
    public bool Matches(Type typeArgument) => true;
    public bool Equals(IsAnyToken? other) => throw new NotImplementedException();
}

You can use this to write Setup and Verify code exactly like It.IsAnyType:

mockMessenger.Setup(x => x.Send(It.IsAny<MyMessage>(), It.IsAny<IsAnyToken>());
...
mockMessenger.Verify(x => x.Send(It.IsAny<MyMessage>(), It.IsAny<IsAnyToken>(), Times.Once);

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Adam Hewitt
Solution 2 Mythgarr