表达式引用不属于模拟对象的方法

本文关键字:方法 对象 模拟 引用 不属于 表达式 | 更新日期: 2023-09-27 18:33:06

我有一个调用另一个 api 服务的 api 服务。当我设置 Mock 对象时,它失败并显示错误:

NotSupportedException:表达式引用不属于模拟对象的方法。

这是代码:

private Mock<IEnumerable<ICarrierApiService<AccountSearchModel>>> _mockCarrierService;
private Mock<IApiService<AccountSearchModel>> _mockApiService;
[SetUp]
public void SetUp()
{
  _mockApiService = new Mock<IApiService<AccountSearchModel>>();
  _mockCarrierService = new Mock<IEnumerable<ICarrierApiService<AccountSearchModel>>>();
  _mockApiService.Setup(x => x.GetFromApiWithQuery(It.IsAny<string>())).Returns(ApiValue());
  // Error occurred when call _mockApiService.GetFromApiWithQuery() in .Select()
  _mockCarrierService.Setup(x => x
            .Select(s => s
                .GetFromApiWithQuery(It.IsAny<string>())).ToList())
                .Returns(new List<IQueryable<AccountSearchModel>> { ApiValue() });
}

阅读了带有最小起订量的表达式测试,但它对我的情况不起作用。如果我删除此_mockCarrierService.Setup(),测试用例可以运行,但由于没有设置有效的List<IQueryable<AccountSearchModel>>,因此NullReferenceException失败。

知道我如何实现这一目标吗?


脚注:当前解决方案

FWIW,这是我目前使用的解决方案。我全心全意地寻求更好的解决问题的方法(直到 Moq 开始支持模拟扩展方法(。

private List<ICarrierApiService<AccountSearchModel>> _mockCarrierService;
private AccountSearchController _mockController;
private Mock<ICarrierApiService<AccountSearchModel>> _mockApiService;
[SetUp]
public void SetUp()
{
   _mockApiService = new Mock<ICarrierApiService<AccountSearchModel>>();
   _carrierServiceMocks = new List<ICarrierApiService<AccountSearchModel>> { _mockApiService.Object };
   _mockApiService.Setup(x => x.GetFromApiWithQuery(It.IsAny<string>())).Returns(ApiValue());
   _mockController = new AccountSearchController(_carrierServiceMocks);
}

脚注:替代模拟框架

我还发现了一个支持模拟扩展方法的商业模拟框架,并链接到操作文档:Telerik JustMock。

表达式引用不属于模拟对象的方法

出现此问题是因为您正在尝试模拟Select方法,该方法是扩展方法,而不是IEnumerable<T>的实例方法。

基本上,没有办法模拟扩展方法。看看这个问题,了解一些你可能会觉得有用的想法。

UPD (12/11/2014(:

若要更深入地了解模拟扩展方法,请考虑以下事项:

    尽管调用扩展方法就
  • 好像它们是扩展类型的实例方法一样,但它们实际上只是一个带有一点语法糖的静态方法。

  • 来自命名空间
  • System.Linq扩展方法作为纯函数实现 - 它们是确定性的,并且没有任何可观察到的副作用。我同意静态方法是邪恶的,除了那些纯函数 - 希望你也同意这个说法:)

  • 那么,给定一个类型为 T 的对象,你将如何实现静态纯函数f(T obj)?只有通过组合为对象T定义的其他纯函数(或实际上任何其他纯函数(,或者通过读取不可变和确定性的全局状态(以保持函数f确定性和无副作用(来实现。实际上,"不可变和确定性的全局状态"有一个更方便的名字——一个常量。

因此,事实证明,如果您遵循静态方法应该是纯函数的规则(看起来Microsoft遵循此规则,至少对于 LINQ 方法而言(,模拟扩展方法f(this T obj)应该可以简化为模拟该扩展方法使用的非静态方法或状态 — 仅仅是因为该扩展方法依赖于其实现中的obj实例方法和状态(可能还依赖于其他纯函数(和/或常量值(。

IEnumerable<T>的情况下,Select()扩展方法是根据foreach语句实现的,而语句又使用GetEnumerator()方法。因此,您可以模拟GetEnumerator(),并为依赖于它的扩展方法实现所需的行为。

你有:

_mockCarrierService = new Mock<IEnumerable<ICarrierApiService<AccountSearchModel>>>();

所以你嘲笑IEnumerable<>.IEnumerable<>拥有的唯一成员是一个方法GetEnumerator()(以及从基接口继承GetEnumerator()具有相同签名的另一个方法(。Select方法实际上是一个扩展方法(如第一个答案中指出的那样(,它是一种静态方法,通过调用GetEnumerator()(可能通过 C# foreach 语句(工作。

可以通过对模拟进行Setup GetEnumerator来使事情正常进行。

然而,简单地使用一个具体的、非模拟的类型要简单得多,它"是"IEnumerable<>,例如List<>。所以试试:

_mockCarrierService = new List<ICarrierApiService<AccountSearchModel>>();

然后将条目添加到List<> 中。您应该添加的是设置GetFromApiWithQuery方法的Mock<ICarrierApiService<AccountSearchModel>>

此外,

如果您需要模拟 IConfiguration,您可以使用以下代码:

var builder = new ConfigurationBuilder()
        .AddInMemoryCollection(new Dictionary<string, string>
        {
            { "your-key", "your value" }
        });
        var config = builder.Build();

由于关于嘲笑IConfiguration的问题将这个答案称为重复,我将在这里贡献我的一角钱。这就是我"模拟"IConfiguration的方式,在我看来,这更干净一些:

    private static IConfiguration GetConfigurationMock(string jsonConfiguration)
    {
        var byteArray = Encoding.UTF8.GetBytes(jsonConfiguration);
        var stream = new MemoryStream(byteArray);
        var conf = new ConfigurationBuilder();
        conf.AddJsonStream(stream);
        var confRoor = conf.Build();
        return confRoor; 
    } 

原始问题:如何设置 IConfigurationRoot 的模拟以返回值