我们最近开始使用MediatR,以便在重构面向客户的大型门户并将其全部转换为C#时,使 Controller 操作变得整洁。作为其一部分,我们也增加了单元测试的覆盖范围,但是在尝试模拟MediatR本身时遇到了一个问题。

该命令做了很多事情来启动一个进程,其中一部分是发送通知。通知本身由其自己的处理程序处理,因此将接受其自己的单元测试,因此我想模拟MediatR,以便this.mediator.Send(message)调用实际上不做任何事情。处理程序确实返回了一个对象,但是在这种情况下我们并不关心它,因此出于所有意图和目的,我们将其视为void返回。我只想验证Send是否已作为测试的一部分被调用。但是,Send方法抛出了NullReferenceException,我不知道为什么。

从版本3开始,MediatR现在在Send上添加了第二个可选参数,CancellationToken,表达式树要求您显式设置它们,因此必须指定一个值。我以前从未遇到过这种情况,在我看来,这可能是问题的一部分,但这可能是我的混淆。

这是一个简化的插图。

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
        }
    }
}

测试
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());
    }
}

我想念什么?我觉得我在某个地方犯了一个根本性的错误,但是我不确定在哪里。

最佳答案

Send方法返回任务时,您需要处理它们的异步操作等待。

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

这意味着您需要让模拟返回一个任务,以允许异步进程继续执行流程
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());

关于c# - 用Moq模拟MediatR 3,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/43249816/

10-12 03:52