Mocking MediatR 3 with Moq

心已入冬 提交于 2021-02-06 15:19:00

问题


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.


回答1:


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());


来源:https://stackoverflow.com/questions/43249816/mocking-mediatr-3-with-moq

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!