2010-08-21 9 views
24

Stiamo utilizzando Moq per testare le nostre classi di servizio, ma siamo bloccati su come testare situazioni in cui un metodo di servizio chiama un altro metodo di servizio della stessa classe. Ho provato a impostare il metodo chiamato in virtuale, ma ancora non riuscivo a capire cosa fare in Moq. Per esempio:Uso di Moq per sovrascrivere i metodi virtuali nella stessa classe

public class RenewalService : IRenewalService 
{ 
    //we've already tested this 
    public virtual DateTime? GetNextRenewalDate(Guid clientId) 
    { 
     DateTime? nextRenewalDate = null; 
     //...<snip> a ton of already tested stuff... 

     return nextRenewalDate; 
    } 

    //but want to test this without needing to mock all 
    //the methods called in the GetNextRenewalDate method 
    public bool IsLastRenewalOfYear(Renewal renewal) 
    { 
     DateTime? nextRenewalDate = GetNextRenewalDate(renewal.Client.Id); 
     if (nextRenewalDate == null) 
      throw new Exceptions.DataIntegrityException("No scheduled renewal date, cannot determine if last renewal of year"); 
     if (nextRenewalDate.Value.Year != renewal.RenewDate.Year) 
      return true; 
     return false; 
    } 
} 

Nell'esempio di cui sopra, il nostro metodo GetNextRenewalDate è piuttosto complicata, e abbiamo già testato unità. Tuttavia, vogliamo testare IsLastRenewalOfYear più semplice senza dover simulare tutto ciò che è necessario per GetNextRenewalDate. Fondamentalmente, vogliamo solo prendere in giro GetNextRenewalDate.

Mi rendo conto che potrei creare una nuova classe che sostituisce GetNextRenewalDate e testare la nuova classe, ma c'è un modo in cui posso sfruttare Moq per rendere più semplice questo?

risposta

41

Probabilmente si può utilizzare beffardo parziale in questo scenario, anche se tutti i metodi avrebbero bisogno di essere virtuale:

var mock = new Moq.Mock<RenewalService>(); 
    mock.Setup(m => m.GetNextRenewalDate(It.IsAny<Guid>())).Returns(null); 
    mock.CallBase = true; 
    var results = mock.Object.IsLastRenewalOfYear(...); 
+0

Questo sembra funzionare ... Non ho nemmeno bisogno di rendere tutto virtuale (l'ho solo mantenuto come sopra). – Andrew

+3

@Andrew. Quanto sopra funziona perché 'Mock.CallBase == true' significa che le chiamate che non corrispondono a una configurazione chiameranno l'implementazione sottostante. Quindi, 'IsLastRenewalOfYear' chiamerà l'implementazione, perché non è virtuale, ma' GetNextRenewalDate' restituirà 'null' perché l'impostazione verrà sempre abbinata. Il seguente codice funzionerà anche se 'IsLastRenewalOfYear' ** è ** virtuale. –

+1

abbiamo usato questo in un altro paio di posti, ha funzionato come un fascino. Grazie! – Andrew

0

Modifica: Sono d'accordo sul fatto che questa non è la risposta giusta per il caso di Andrew. Voglio lasciare questa risposta qui per la discussione dei commenti. Si prega di non votare più basso :)

Prima edit:

quadri oggetto Generalmente finte non sono progettati per semplificare lo scenario di classe singola, sono progettati per isolare il codice in modo da poter testare una singola classe.

Se si tenta di utilizzare un framework di oggetti di simulazione per risolvere questo problema, il framework creerà una classe derivata e sovraccaricherà tale metodo. L'unica differenza è che puoi farlo in circa 3 righe anziché 5, perché non dovrai creare la definizione della classe derivata.

Se si desidera utilizzare oggetti mock per isolare questo comportamento, è necessario suddividere questa classe un po '. La logica GetNextRenewalDate potrebbe vivere al di fuori dell'oggetto RenewalService.

Il fatto che stai incontrando questo problema può mostrare che esiste un design più semplice o più finemente ancora da scoprire. Trovare una classe un po 'meno concreta, con un nome come "manager" o "servizio" è spesso un suggerimento per poter suddividere il tuo design in classi più piccole e ricavarne una migliore riusabilità e manutenibilità.

+0

io non sono sicuro se la rottura nostro design renderebbe più gestibile .. Se passassimo da 5.000 classi (approssimativamente ciò che abbiamo attualmente) a 10.000 classi, difficilmente sarebbe più facile navigare. – Andrew

+0

rompendo il progetto semplicemente per renderlo più testabile all'interno di un set di strumenti specifico sembra una cattiva idea. Scendendo lungo questo percorso, raramente i metodi chiamano altri metodi pubblici all'interno di una classe. Com'è quel buon design? –

+0

@Andrew, Jess: Non conosco la sua app o le sue esigenze, quindi sentitevi liberi di prendere o lasciare il mio consiglio. Sì, spezzarlo solo per avere lezioni di un metodo è sciocco, ma il resto del mio consiglio vale ancora. Spesso, quando ho visto una classe "manager" o "servizio", le astrazioni non sono nei posti giusti. Se si dovesse fare un refactoring di questo tipo, i metodi di altre classi potrebbero finire qui, o questi metodi potrebbero finire in altre classi, facendo sparire completamente questa classe. –

0
var mock = new Moq.Mock<RenewalService> { CallBase = true }; 
mock.Setup(m => m.GetNextRenewalDate(It.IsAny<Guid>())).Returns(null); 
var results = mock.Object.IsLastRenewalOfYear(...); 
Problemi correlati