Controller per unit test senza mock

Aug 29 2020

Ho eseguito molti test di scrittura utilizzando Mocks, quindi ho imparato che rende difficile il refactoring a causa dell'accoppiamento di implementazione inerente a Mocks. Ho letto molto sull'argomento stasera, ma non riesco a vedere un modo per testare unità di classi di tipo facciata DI senza usare Mocks. Vorrei conoscere il modo corretto per testare qualcosa come un controller in un'applicazione MVC (ad esempio, Spring) con l'intento di non utilizzare i mock.

Ad esempio, dato un controller che dipende da un servizio. Come posso testare il controller senza deridere il servizio.

public class ThingController {
  ..
  public getAllThings() {
    return thingService.getAllThings();
  }
}

Per testare getAllThings(), il mio pensiero naturale è di testarlo iniettando un mock di ThingService al controller che restituisce alcune cose quando getAllThings()viene invocato .. Ma posso dire che questo fa immediatamente dipendere il test dall'implementazione del controller nel chiamare il servizio getAllThings().

Qual è l'approccio non accoppiato preferito? È che in un caso come questo un test "unitario" non vale la pena? E invece preferiamo un test "Component" che imposta un servizio effettivo (o falso) con i dati e quindi verifica che il controller restituisca tutte le cose popolate in detto servizio iniettato?

Modifica: per ulteriori informazioni / spiegazioni, i test del controller (in primavera) di solito non sono test unitari puri, poiché vengono eseguiti utilizzando @WebMvcTest, ma i servizi vengono iniettati come mock. E per evitare di deridere l'implementazione, sto cercando un'alternativa. Non ho menzionato le specifiche, perché volevo includere altri scenari come i servizi di unit test: in questo caso, voglio evitare di deridere i repository iniettati.

Risposte

7 Euphoric Aug 29 2020 at 19:30

A meno che tu non voglia riprogettare l'intera soluzione, l'unica altra opzione è usare Fakes invece di mock.

Fake è la reale implementazione di un'astrazione, ma che è intesa per test e ha funzionalità reali limitate. Il miglior esempio è l'implementazione del repository in memoria invece del repository che utilizza SQL. O l'implementazione di un servizio falso che si comporta come un servizio reale, ma ha l'archiviazione dei dati in memoria e manca di tutti i campanelli del servizio reale.

Questa falsa implementazione può quindi essere riutilizzata in tutti i luoghi in cui è possibile utilizzare l'astrazione. E in alcuni scenari, è possibile scrivere test che verificano che le implementazioni false e reali si comportino allo stesso modo.

I falsi non causano un accoppiamento stretto tra test e codice, come fanno i mock. Questo perché i test conoscono solo l'istanza di test utilizzata, non l'interfaccia implementata e utilizzata. Questo è un dettaglio nascosto. E quando sono previste modifiche all'implementazione reale, la semplice modifica del falso in un unico posto dovrebbe propagarsi correttamente in tutti i tuoi test, quindi non devi ricontrollare tutti i tuoi test che hanno aspettative corrette nei loro mock.

Ora qualcuno potrebbe dire "Questo non è un vero test unitario". e anche se potrei essere in qualche modo d'accordo, non mi interessa. L'implementazione falsa si traduce in test veloci, isolati, indipendenti e di facile manutenzione. Tutto quello che cerco in buona prova. Unità o no.

5 GregBurghardt Aug 29 2020 at 17:56

Non è possibile testare un controller utilizzando un'istanza reale di un servizio e chiamarlo comunque unit test. Gli unit test sono pensati per essere eseguiti con dati in memoria e un singolo test dovrebbe essere eseguito entro pochi millisecondi (al massimo). I test unitari non dovrebbero inoltre utilizzare risorse fuori processo, come file system o database.

Questo non vuol dire che testare un controller senza mock sia un test non valido. Sarebbe considerato un test di integrazione. Il vantaggio di un tale test è che colpisce più codice. Lo svantaggio è che i test di integrazione sono più difficili da eseguire il debug perché richiedono più codice.