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

时间:2014-12-04 04:08:10

标签: c# linq unit-testing expression moq

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

  

NotSupportedException:expression引用一个不属于模拟对象的方法。

这是代码:

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

我看了Expression testing with Moq,但它对我的案子没有用。如果我删除此_mockCarrierService.Setup(),则测试用例可以运行,但由于NullReferenceException设置失败而导致失败,因为它没有设置List<IQueryable<AccountSearchModel>>

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


脚注:当前解决方案

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

4 个答案:

答案 0 :(得分:63)

出现此问题的原因是您尝试模拟Select方法,extension method,而不是IEnumerable<T>的实例方法。

基本上,没有办法模拟扩展方法。请查看this question,了解您可能会觉得有用的一些想法。

UPD(12/11/2014):

要了解有关模拟扩展方法的更多信息,请考虑以下事项:

  • 虽然扩展方法被称为扩展类型的实例方法,但它们实际上只是一个带有一点语法糖的静态方法。

  • System.Linq命名空间中的扩展方法实现为pure functions - 它们是确定性的,并且没有任何可观察的side effects。我同意static methods are evil, except those that are pure functions - 希望你也同意这句话:)

  • 因此,给定T类型的对象,您将如何实现静态纯函数f(T obj)?只有通过组合为对象T(或任何其他纯函数,实际上)定义的其他纯函数,或者通过读取不可变和确定性全局状态(以保持函数f确定性和侧面 - 效果免费)。实际上,“不可变和确定性的全局状态”具有更方便的名称 - 常量。

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

如果是IEnumerable<T>Select()扩展方法foreachGetEnumerator()语句而言,GetEnumerator()语句依次使用{{1}}方法。因此,您可以模拟{{1}}并为依赖它的扩展方法实现所需的行为。

答案 1 :(得分:7)

你有:

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

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

可以通过模拟Setup GetEnumerator来使事情有效。

但是,简单地使用“is”IEnumerable<>的具体非模拟类型(例如List<>)要简单得多。所以试试:

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

然后在List<>中添加一个条目。您应该添加的内容是Mock<ICarrierApiService<AccountSearchModel>>,其中GetFromApiWithQuery方法已设置。

答案 2 :(得分:0)

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

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

答案 3 :(得分:0)

由于关于嘲笑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; 
    } 

原始问题:How to setup Mock of IConfigurationRoot to return value