Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
240 views
in Technique[技术] by (71.8m points)

c# - Expression references a method that does not belong to the mocked object

I have an api service that calls another api service. When I set up the Mock objects, it failed with an error:

NotSupportedException: expression references a method that does not belong to the mocked object.

This is the code:

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

I read Expression testing with Moq but it didn't work for my case. If I remove this _mockCarrierService.Setup(), the test case can run but fails with a NullReferenceException because it didn't have a valid List<IQueryable<AccountSearchModel>> set up.

Any idea how I can achieve this?


Footnote: Current Solution

FWIW, here's the solution that I currently use. I am all ears for a better approach to the issue (until Moq starts supporting mocking extension methods).

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

Footnote: alternative mocking framework

I've also found a commercial mocking framework that supports mocking extension method and link to the how-to docs: Telerik JustMock.

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

This problem occurs because you are trying to mock Select method, which is an extension method, not an instance method of IEnumerable<T>.

Basically, there is no way to mock an extension method. Have a look at this question for some ideas that you may find useful.

UPD (12/11/2014):

To gain more understanding on mocking extension methods, think about the following:

  • Although extension methods are called as if they were instance methods on the extended type, they are actually just a static methods with a bit of syntactic sugar.

  • Extension methods from System.Linq namespace are implemented as pure functions — they are deterministic and they don't have any observable side effects. I agree that static methods are evil, except those that are pure functions — hope you would agree with this statement too :)

  • So, given an object of type T, how would you implement static pure function f(T obj)? It is only possible by combining other pure functions that are defined for object T (or any other pure functions, actually), or by reading immutable and deterministic global state (to keep function f deterministic and side-effect-free). Actually, "immutable and deterministic global state" has more convenient name — a constant.

So, it turns out that if you follow the rule that static methods should be pure functions (and it looks like Microsoft follows this rule, at least for the LINQ methods), mocking an extension method f(this T obj) should be reducible to mocking non-static methods or state used by that extension method — simply because that extension method relies on the obj instance methods and state in its implementation (and possibly on the other pure functions and/or constant values).

In case of IEnumerable<T>, Select() extension method is implemented in terms of foreach statement which, in turn, uses GetEnumerator() method. So you can mock GetEnumerator() and achieve required behavior for extension methods that rely on it.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...