Mocking MediatR 3 with Moq

21,223

You need to handle the await of the async operation of the Send methods as they return tasks.

/// <summary>
/// Asynchronously send a request to a single handler
/// </summary>
/// <typeparam name="TResponse">Response type</typeparam>
/// <param name="request">Request object</param>
/// <param name="cancellationToken">Optional cancellation token</param>
/// <returns>A task that represents the send operation. The task result contains the handler response</returns>
Task<TResponse> Send<TResponse>(IRequest<TResponse> request, CancellationToken cancellationToken = default(CancellationToken));

/// <summary>
/// Asynchronously send a request to a single handler without expecting a response
/// </summary>
/// <param name="request">Request object</param>
/// <param name="cancellationToken">Optional cancellation token</param>
/// <returns>A task that represents the send operation.</returns>
Task Send(IRequest request, CancellationToken cancellationToken = default(CancellationToken));

That means you need to have the mock return a task to allow the async process to continue the flow

mediator
    .Setup(m => m.Send(It.IsAny<TransferNotificationCommand>(), It.IsAny<CancellationToken>()))
    .ReturnsAsync(new Notification()) //<-- return Task to allow await to continue
    .Verifiable("Notification was not sent.");

//...other code removed for brevity

mediator.Verify(x => x.Send(It.IsAny<CreateIsaTransferNotificationCommand>(), It.IsAny<CancellationToken>()), Times.Once());
Share:
21,223

Related videos on Youtube

Steve Pettifer
Author by

Steve Pettifer

Updated on July 09, 2022

Comments

  • Steve Pettifer
    Steve Pettifer almost 2 years

    We've recently started using MediatR to allow us to de-clutter controller actions as we re-factor a large customer facing portal and convert it all to C#. As part of this we are increasing our unit test coverage as well, but I've hit a problem when trying to mock MediatR itself.

    The command does a bunch of stuff to initiate a process and part of this is sending a notification. The notification itself is dealt with by its own handler and therefore would be subject to its own unit test so I want to mock MediatR so that the this.mediator.Send(message) call doesn't really do anything. The handler does return an object but we don't care about it in this context so to all intents and purposes we are treating it as a void return. I just want to verify that Send has been called once as part of the test. However, the Send method is throwing a NullReferenceException and I don't know why.

    As of version 3, MediatR now takes a second optional parameter on Send, a CancellationToken, and expression trees require you to explicitly set them so you must specify a value. I've not encountered this before and in my mind I feel that this might be part of the problem but this may be conflation on my part.

    Here's a cut down illustration.

    SUT

    public class TransferHandler : IAsyncRequestHandler<TransferCommand, TransferResult>
    {
        private readonly IMediator mediator;
    
        public TransferHandler(IMediator mediator)
        {
            this.mediator = mediator;
        }
    
        public async Task<TransferResult> Handle(TransferCommand message)
        {
            // Other stuff.
            var notification = new TransferNotificationCommand()
            {
                ClientId = message.clientId,
                OfficeId = message.OfficeId,
                AuthorityFileId = letter?.Id
            };
    
            await this.mediator.Send(notification);    // <=== This is where we get a NullReferenceException, even though nothing is actually null (that I can see).
    
            return new TransferResult()
            {
                Transfer = transfer,
                FileId = letter?.Id
            }
        }
    }
    

    Test

    public class TransferHandlerTests
    {
        [Theory]
        [AutoData]
        public async void HandlerCreatesTransfer(Mock<IMediator> mockMediator)
        {
            // Note that default(CancellationToken) is the default value of the optional argument.
            mockMediator.Setup(m => m.Send(It.IsAny<TransferNotificationCommand>(), default(CancellationToken))).Verifiable("Notification was not sent.");
    
            var handler = new TransferHandler(mockMediator.Object);
    
            var actual = await handler.Handle(message);
    
            mockMediator.Verify(x => x.Send(It.IsAny<CreateIsaTransferNotificationCommand>(), default(CancellationToken)), Times.Once());
        }
    }
    

    What am I missing? I feel like I've made a fundamental mistake somewhere but I'm not sure where.

    • OutstandingBill
      OutstandingBill about 4 years
      Should that parameter in the Test snippet be called mediator?
    • Steve Pettifer
      Steve Pettifer about 4 years
      @OutstandingBill No it was a typo that no one spotted! I've edited it
  • Steve Pettifer
    Steve Pettifer about 7 years
    Like I said - it had to be something fundamental and stupid!! I was assuming that since I didn't care about the return I could omit it, when in fact I hand't told the mock what to do at all so no task was returned. Makes perfect sense now, thank you! I have edited your answer though as the Returns was slightly out.
  • Nkosi
    Nkosi about 7 years
    @StevePettifer, I was uncertain what your response was based on provided code in OP. Your update is accurate.