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

dis*_*ame 38 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() });
}
Run Code Online (Sandbox Code Playgroud)

用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);
}
Run Code Online (Sandbox Code Playgroud)

脚注:替代模拟框架

我还发现了一个商业模拟框架,它支持模拟扩展方法和指向操作方法文档的链接:Telerik JustMock.

Ser*_*diy 68

出现此问题的原因是您尝试模拟Select方法,这是一种扩展方法,而不是实例方法IEnumerable<T>.

基本上,没有办法模拟扩展方法.看看这个问题,你可能会发现一些有用的想法.

UPD(12/11/2014):

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

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

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

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

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

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


Jep*_*sen 7

你有:

_mockCarrierService = new Mock<IEnumerable<ICarrierApiService<AccountSearchModel>>>();
Run Code Online (Sandbox Code Playgroud)

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

有可能使事情做工作SetupGetEnumerator你的模拟.

然而,这是很简单简单地用一个具体的,非模拟类型的"是" IEnumerable<>,如List<>.所以尝试:

_mockCarrierService = new List<ICarrierApiService<AccountSearchModel>>();
Run Code Online (Sandbox Code Playgroud)

然后添加一个条目List<>.你应该补充,是Mock<ICarrierApiService<AccountSearchModel>>在其GetFromApiWithQuery方法是设置.