使用 Moq 模拟 MediatR 3

Posted

技术标签:

【中文标题】使用 Moq 模拟 MediatR 3【英文标题】:Mocking MediatR 3 with Moq 【发布时间】:2017-09-01 04:01:09 【问题描述】:

我们最近开始使用 MediatR 来整理控制器操作,因为我们重构了一个面向客户的大型门户并将其全部转换为 C#。作为其中的一部分,我们也在增加我们的单元测试覆盖率,但我在尝试模拟 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());
    

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

【问题讨论】:

Test sn-p中的那个参数应该叫mediator吗? @OutstandingBill 不,这是一个没有人发现的错字!我已经编辑过了 【参考方案1】:

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

【讨论】:

就像我说的 - 它必须是基本的和愚蠢的!我假设由于我不关心返回我可以省略它,而实际上我根本没有告诉模拟要做什么,所以没有返回任何任务。现在很有意义,谢谢!我已经编辑了你的答案,但由于回报略显不足。 @StevePettifer,根据 OP 中提供的代码,我不确定您的回复是什么。您的更新是准确的。

以上是关于使用 Moq 模拟 MediatR 3的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 MOQ 框架在 c# 中模拟静态方法?

如何使用 moq 模拟 ConfigurationManager.AppSettings

使用 Moq 模拟扩展方法

使用 Moq 模拟 NHibernate ISession

使用 moq 模拟虚拟只读属性

如何使用 Moq 框架模拟 ModelState.IsValid?