2014-12-04 19 views
31

我有一個調用另一個api服務的api服務。當我設置了模擬對象,它失敗,出現錯誤:表達式引用不屬於模擬對象的方法

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

這是代碼:

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(),則測試用例可以運行,但由於沒有設置有效的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

回答

48

回到頂端發生此問題是因爲您試圖模擬方法,這是一個extension method,而不是一個實例方法IEnumerable<T>

基本上,沒有辦法模擬一個擴展方法。看看this question的一些想法,你可能會覺得有用。

UPD(2014年12月11日):

爲了獲得在嘲諷擴展方法的更多的理解,思考如下:

  • 雖然擴展方法被稱爲好像他們是在實例方法擴展類型,它們實際上只是一個帶有一些語法糖的靜態方法。

  • System.Linq命名空間的擴展方法實現爲pure functions - 它們是確定性的,它們沒有任何可觀察的side effects。我同意static methods are evil, except those that are pure functions - 希望你這個說法太:)

  • 因此,考慮到T類型的對象,你將如何實現靜態純函數f(T obj)同意嗎?只有通過爲對象T(或任何其他純函數,實際上)定義的其他純函數或通過讀取不可變且確定性的全局狀態(保持函數f確定性和無副作用)纔有可能。實際上,「不變的和確定性的全球狀態」有更方便的名稱 - 一個常數。

所以,事實證明,如果你按照規則靜態方法應該是純功能(它看起來像微軟遵循這個規則,至少對LINQ方法),嘲諷的擴展方法f(this T obj)應可簡化爲模擬非擴展方法所使用的非靜態方法或狀態 - 僅僅因爲該擴展方法依賴於其實現中的實例方法和狀態(可能還有其他純函數和/或常量值)。

IEnumerable<T>情況下,Select()擴展方法是在foreach語句,這反過來,使用GetEnumerator()方法方面implemented。所以你可以模擬GetEnumerator()並獲得所需的依賴它的擴展方法的行爲。

+0

感謝您的幫助。我從這裏提到的那個作者那裏找到了另一篇文章。如果我添加更復雜的[Fact]方法,它可能會起作用。我必須嘗試去確認。 – 2014-12-04 12:14:01

+0

@IsabelHM,我更新了答案,所以你可能會從不同的角度考慮問題:) – 2014-12-11 15:55:12

+0

謝謝你的信息。 – 2014-12-16 22:05:26

6

您有:

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

所以你嘲笑IEnumerable<>。唯一的成員IEnumerable<>有一個方法GetEnumerator()(加上從基本接口繼承的相同簽名GetEnumerator()的另一種方法)。 Select方法實際上是一種擴展方法(正如在第一個答案中指出的那樣),這是一種通過調用GetEnumerator()(可能通過C#foreach語句)工作的靜態方法。

有可能使事情的工作,即你的模擬做的GetEnumeratorSetup

然而,簡單地使用「是」IEnumerable<>的具體非模擬類型(諸如List<>)要簡單得多。因此,嘗試:

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

然後添加到List<>的條目。您應該添加的是Mock<ICarrierApiService<AccountSearchModel>>,其中GetFromApiWithQuery方法已設置。

+0

是的,這是我在閱讀他的答案中提到的文章@Sergey後意識到的。我必須嘗試去確認。謝謝! – 2014-12-04 14:55:12

相關問題