Usando i corretti pattern di progettazione di una classe, e utilizzando le opportune fake class, è sempre possibile fare tutti gli unit test del caso.
Come visto le classi menzognere (l'altro nome che abbiamo dato alle fake class) non fanno altro che implementare delle interfacce, in modo da permettere al codice in test di interagire correttamente con le funzionalità esterne, simulandole.
Tutto meraviglioso però è innegabile che scrivere fake class è una rottura di scatole infinita, e direi che dopo un po' ti toglie la voglia di implementare qualsiasi unit test.
Mi permetto di sottolineare il punto: non è sbagliato implementare unit test con l'ausili di fake class, semplicemente questa è un’attività generalmente è molto onerosa.
Per ovviare a questo sforzo ci viene in aiuto un framework che è in grado di aiutarci a bestia nella creazione di classi fake: questo meraviglioso alleato si chiama Moq.
Un primo esempio di utilizzo
In poche parole Moq permette di creare quelli che vengono chiamati oggetti Mock, che sono classi che simulano il comportamento di altri oggetti.
Questo tipo di oggetti sono, sono in tutto e per tutto delle fake class, che permettono di simulare il comportamento della classe reale. La sottile differenza è che senza Moq prima abbiamo dovuto creare le fake class da zero. mentre qui usando gli oggetti Mock è possibile solo configurare il comportamento di questi oggetti impostando per ogni metodo il valore di ritorno che deve fornire in associazione a determinati parametri di ingresso.
Subito un piccolo esempio.
Sopra il test equivalente a quello scritto nel post precedente con l'ausilio della classe fake scritta esplicitamente.
Si nota subito che usando il framework le cose sono molto più semplici e compatte.
Iniziamo a vedere come ho creato l'oggetto InvioMailFake: con quella semplice chiamata ho creato un oggetto Mock nonchè un'istanza della classe che implementa l'interfaccia specificata, che sarà accessibile tramite la proprietà object. Ogni parametro che abbisogna di un oggetto che implementa l'interfaccia IInvioMail non avrà nulla da obiettare ad accettare l'oggetto invioMailFake.Object.
Una volta creato l’oggetto Mock è possibile successivamente esprimere come questa debba comportarsi quando vengono richiamati i suoi metodi: detto in altri termini occorre configurare il comportamento dell'oggetto Mock appena creato.
Nel nostro caso si è interessati che quando si chiamata il metodo SendMail, passando come parametro qualsiasi stringa, venga restituito true.
Implementare questo comportamento è possibile con l'ausilio del metodo setup, nonchè usando il metodo statico IsAny della classe It.
Se per caso si volesse impostare lo stesso oggetto mock affinché mi restituisca true solo se si passa come argomento Questo indirizzo email è protetto dagli spambots. È necessario abilitare JavaScript per vederlo. è possibile fare una cosa come nel seguito.
invioMailFake.Setup(x => x.SendEmail(It.IsAny(), It.IsAny())).Returns<string,string>((s,t)=>s== "Questo indirizzo email è protetto dagli spambots. È necessario abilitare JavaScript per vederlo.");
Successivamente si è provveduto a creare in modo simile anche repositoryGenericoFake . Una volta creati e configurati le classi mock è il momento di passarlo al metodo da testare: si ottiene questo con usando la proprietà object.
Rimane il problema di come verificare se qualche metodo di qualche oggetto è stato richiamato dal codice oggetto di unit test: in precedenza avevamo usato un counter nella classe fake, qui è possibiel fare la stessa cosa con l'ausilio del metodo Verify.
Nel caso in analisi si verifica che il metodo SendMail è stato richiamato solo una volta passandogli una qualsiasi stringa come parametro.
Volendo era possibile verificare se il metodo sia stato chiamato con la mail di test: questo è possibile adottando la chiamata nel seguito.
invioMailFake.Verify(x => x.SendEmail("Questo indirizzo email è protetto dagli spambots. È necessario abilitare JavaScript per vederlo.", It.IsAny());
Ora la semplicità di utilizzo è sconcertante, ma una domanda sorge spontanea: cosa succede se richiamo dei metodi dell'oggetto mock non esplicitamente configurati con setup ?
E' qui che entra in campo il parametro MockBehavior. Questo parametro viene passato nel costruttore dell’oggetto mock, e non è obbligatorio.
.....
Mock invioMailFake = new Mock(MockBehavior.Strict);
....
Mock<IRepositoryGenerico> repositoryGenericoFake = new Mock<IRepositoryGenerico>(MockBehavior.Loose);
Se il parametro è Strict allora se per caso si richiama un metodo che non è stato configurato esplicitamente con setup si ottiene il comportamento che il framework moq emetterà un’eccezione bloccante: se Loose invece non si otterrà alcuna eccezione.
Di default, se non diversamente specificato, viene utilizzato Loose. In questo caso il framework permetterà di richiamare metodi non esplicitamente configurati, e restituirà i valori di default per il tipo di ritorno (per esempio null per gli oggetti, o 0 per interi, e così via).
Quale valore assegnare a questo parametro è un po' gusto personale e dipende dal contesto di utilizzo: io comunque preferisco sempre forzarlo a Strict, perché desidero sempre sapere perfettamente ogni metodo viene richiamato e con che parametri.
Linkografia
Informatica pressapochista - Un viaggio negli unit-test: Introduzione - Prima parte
Informatica pressapochista - Un viaggio negli unit-test: xUnit e dintorni – Seconda parte
Informatica pressapochista - Un viaggio negli unit-test: Fake class e dintorni - Terza parte
Informatica pressapochista - Un viaggio negli unit-test: Repository pattern - Quarta parte
Informatica pressapochista - Un viaggio negli unit-test: Non male le fake class.... – Quinta parte
Git Hub Page InformaticaPressapochista
Moq4 - Quickstart
Dean Himes' blog Basic Introduction To Writing Unit Tests With MOQ
DZone - How to Use Moq to Ease Unit Testing