2014-12-04 7 views
31

Ho un servizio API che chiama un altro servizio API. Quando ho creato gli oggetti Mock, non è riuscito con un errore:Espressione fa riferimento a un metodo che non appartiene all'oggetto mocked

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

Questo è il codice:

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

ho letto Expression testing with Moq, ma non ha funzionato per il mio caso. Se rimuovo questo _mockCarrierService.Setup(), il test case può essere eseguito ma fallisce con un NullReferenceException perché non aveva una configurazione valida List<IQueryable<AccountSearchModel>>.

Qualche idea su come posso raggiungere questo obiettivo?


Nota: soluzione attuale

FWIW, ecco la soluzione che ho attualmente in uso. Sono pronto per un approccio migliore al problema (fino a quando Moq inizia a supportare i metodi di estensione fittizia).

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

Nota: quadro di scherno alternativa

Ho anche trovato un quadro di scherno commerciale che supporta beffardo metodo di estensione e collegamento per l'how-to docs: Telerik JustMock.

risposta

48

Questo problema si verifica perché si sta tentando di simulare il metodo Select, che è un extension method, non un metodo di istanza di IEnumerable<T>.

Fondamentalmente, non c'è modo di prendere in giro un metodo di estensione. Dai un'occhiata a this question per alcune idee che potresti trovare utili.

UPD (2014/12/11):

Per guadagnare più comprensione sulla beffardo metodi di estensione, pensare a quanto segue:

  • Anche se i metodi di estensione sono chiamati come se fossero metodi di istanza su il tipo esteso, in realtà sono solo metodi statici con un po 'di zucchero sintattico.

  • I metodi di estensione dallo spazio dei nomi System.Linq sono implementati come pure functions - sono deterministici e non hanno alcun osservabile side effects. Sono d'accordo che static methods are evil, except those that are pure functions - la speranza che si sarebbero d'accordo con questa affermazione troppo :)

  • Quindi, dato un oggetto di tipo T, come è possibile implementare statica pura funzione f(T obj)? È possibile solo combinando altre funzioni pure definite per l'oggetto T (o qualsiasi altra funzione pura, in realtà) o leggendo lo stato globale immutabile e deterministico (per mantenere la funzione f deterministica e senza effetti collaterali). In realtà, "stato globale immutabile e deterministico" ha un nome più conveniente: una costante.

Così, si scopre che se si segue la regola che i metodi statici dovrebbero essere funzioni pure (e sembra che Microsoft segue questa regola, almeno per i metodi di LINQ), beffardo un metodo di estensione f(this T obj)dovrebbe essere riduttivo per deridere metodi non statici o stato utilizzato da quel metodo di estensione - semplicemente perché quel metodo di estensione si basa sui metodi di istanza obj e lo stato nella sua implementazione (e possibilmente sull'altro pure funzioni e/o valori costanti).

In caso di IEnumerable<T>, Select() metodo di estensione è implemented in termini di foreach dichiarazione che, a sua volta, utilizza GetEnumerator() metodo. Quindi puoi prendere in giro GetEnumerator() e ottenere il comportamento richiesto per i metodi di estensione che si basano su di esso.

+0

Grazie per l'aiuto. Ho trovato un altro articolo dello stesso autore di quello che hai menzionato qui. Potrebbe funzionare se aggiungo un metodo più complicato [Fact] ad esso. Devo cercare di sapere per certo. –

+0

@IsabelHM, ho aggiornato la risposta, quindi potresti pensare al problema da una prospettiva diversa :) –

+0

Grazie per le informazioni. –

6

si dispone di:

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

Così si deridono IEnumerable<>. L'unico membro IEnumerable<> è un metodo GetEnumerator() (più un altro metodo con la stessa firma GetEnumerator() ereditato dall'interfaccia di base). Il metodo Select è in realtà un metodo di estensione (come indicato nella prima risposta), che è un metodo statico che funziona chiamando lo GetEnumerator() (eventualmente attraverso l'istruzione C# foreach).

È possibile far funzionare le cose eseguendo Setup di GetEnumerator sul proprio finto.

Tuttavia, è molto più semplice utilizzare semplicemente un tipo di tipo non simmetrico che "è" IEnumerable<>, ad esempio List<>. In modo da provare:

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

Quindi aggiungere una voce al List<>. Quello che dovresti aggiungere è un Mock<ICarrierApiService<AccountSearchModel>> su cui è impostato il metodo GetFromApiWithQuery.

+0

sì, questo è quello che ho capito dopo aver letto l'articolo @Sergey menzionato nella sua risposta. Devo cercare di sapere per certo. Grazie! –

Problemi correlati