【问题标题】:Trying To Simplify Moq Setup Syntax With Helper Methods尝试使用辅助方法简化 Moq 设置语法
【发布时间】:2020-08-18 19:01:57
【问题描述】:

我们的单元测试中有很多重复设置,因为我们使用的是 MediatR 库。我们经常有这样的行:

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

我想做一个扩展方法来简化语法以更加强调Command,如果我们愿意的话,仍然可以让我们有条件,比如:

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

此代码构建,但它不起作用(模拟始终返回 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>()));
}

从根本上说:如何将参数传递给稍后进入表达式主体的方法?是否有一种受支持的方法可以为常用设置制作像这样的起订量“助手”功能?

【问题讨论】:

  • 这可能是您的请求不具有可比性,如果您在单元测试中模拟一个请求,然后在您的测试代码中创建一个新请求,那么您将无法获得设置的引用匹配在寻找。但我们需要查看更多代码以确定是否发生了这种情况。
  • 不要模拟 mediatr - 它是纯 c# 代码,没有外部依赖项,不需要模拟。模拟仅适用于外部资源

标签: c# unit-testing moq linq-expressions mediatr


【解决方案1】:

如果您只是通过委托推迟对参数的评估,您现有的扩展方法将起作用:

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

用作

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

从根本上说,在最常见的用例中,它的行为应该与 Jason 建议的答案相同。不过,这并不是真正记录在案的 Moq 行为,因此将 It.Is 与表达式树一起使用会更安全。

好的,但是为什么呢?

此处推迟对参数的评估可确保在评估 It.IsAny 方法调用之前创建“匹配器上下文”。这确保 Moq 可以在遇到 IsAnyIs 表达式时注意到正在评估哪个参数。

It.Is 表达式自然会强制执行此操作,因为它需要一个表达式树,永远不会急切地求值。

另一种选择是手动构建表达式树,但是围绕它构建一个很好的 API 表面区域可能仍然需要对参数进行某种延迟评估,因此它可能不会比现有的方法带来太多收益答案。

【讨论】:

    【解决方案2】:

    我认为这是不可能的,因为Setup 需要Expression。当您传递这样的参数时,表达式树与预期不符。 moq 中有一些方法可以绕过类型安全并使用 Moq.Protected.ItExpr 自己构建表达式树,但据我所知,它们仅限于受保护的成员。

    一种解决方法是执行以下操作:

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2012-11-11
      • 1970-01-01
      • 2010-12-24
      • 2011-09-22
      • 1970-01-01
      • 1970-01-01
      • 2011-11-20
      • 1970-01-01
      相关资源
      最近更新 更多