Trying To Simplify Moq Setup Syntax With Helper Methods

Viewed 445

Our unit tests have a lot of repetitive setups in them because we're using the MediatR library. We frequently have lines that look like this:

mockMediator.Setup(m => m.Send(It.IsAny<Command>(), It.IsAny<CancellationToken>())
    .ReturnsAsync("ok");

I would like to make an extension method that simplifies the syntax to put more emphasis on Command, and still lets us be conditional if we want to, like:

mockMediator.SetupSend(It.IsAny<Command>())
    .ReturnsAsync("ok");
// or
mockMediator.SetupSend(It.Is<Command>(c => c.IsSomething))
    .ReturnsAsync("ok");

This code builds, however it does not work (the mock always returns null):

public static ISetup<IMediator, Task<TResult>> SetupRequest<TResult>(this Mock<IMediator> mockMediator, IRequest<TResult> request) {
    return mockMediator.Setup(m => m.Send(request, It.IsAny<CancellationToken>()));
}

Fundamentally: how can I pass a parameter into a method that goes into an expression body later? Is there a supported way to make Moq "helper" functions like this for commonly-used setups?

2 Answers

Your existing extension method will work if you just defer the evaluation of the parameter via a delegate:

public static ISetup<IMediator, Task<TResult>> SetupRequest<TResult>(this Mock<IMediator> mockMediator, Func<IRequest<TResult>> request) {
    return mockMediator.Setup(m => m.Send(request.Invoke(), It.IsAny<CancellationToken>()));
}

used as

mockMediator.SetupSend(() => It.IsAny<Command>())
    .ReturnsAsync("ok");
// or
mockMediator.SetupSend(() => It.Is<Command>(c => c.IsSomething))
    .ReturnsAsync("ok");

Fundamentally, it should behave the same as the suggested answer from Jason in the most common use cases. This isn't really documented behavior of Moq though, so utilizing It.Is with expression trees is going to be safer.

OK, but why?

Deferring evaluation of the parameter here ensures that the 'matcher context' is created before the It.IsAny method call is evaluated. This ensures that Moq can note which parameter was being evaluated when the IsAny or Is expression is encountered.

The It.Is expression forces this naturally, as it takes an expression tree, which will never be eagerly evaluated.

Another option is to build the expression trees manually, but building a nice API surface area around that will probably still involve the need to have some sort of deferred evaluation of parameters, so it's probably not going to bring much gain over the existing answers.

I don't think that this is going to be possible because Setup takes an Expression. When you pass a parameter like that the expression tree is not as expected. There are methods within moq to bypass type safety and build the expression trees yourself with Moq.Protected.ItExpr, but as far as I can tell they are restricted to protected members.

One work around is to do something like this:

using System;
using System.Linq.Expressions;
using System.Threading;
using System.Threading.Tasks;
using MediatR;
using Moq;
using Moq.Language.Flow;

namespace ConsoleApp11
{
    public class MyRequest : IRequest<int> {}

    class Program
    {
        static async Task Main(string[] args)
        {
            var mockMediator = new Mock<IMediator>();

            mockMediator.SetupRequest<int>(r => true).ReturnsAsync(55);
            var a = await mockMediator.Object.Send(new MyRequest(), CancellationToken.None);
        }
    }

    public static class MockMediatorExtensions
    {
        public static ISetup<IMediator, Task<TResult>> SetupRequest<TResult>(this Mock<IMediator> mediator, Expression<Func<IRequest<TResult>, bool>> isRequest)
        {
            return mediator.Setup(m => m.Send(It.Is(isRequest), It.IsAny<CancellationToken>()));
        }
    }
}

Related