PowerMock (Android)

Spesso per scrivere test di unità si incontrano difficoltà e a volte dobbiamo sacrificare un buon design al solo scopo del testing. Un esempio è rendere pubblici attributi o metodi che altrimenti con JUnit e altri Mock framework non potremmo raggiungere. Altri casi sono relativi al fatto di non poter usare metodi o classi final, metodi statici. Una soluzione a questi problemi è PowerMock!

Logo di PowerMock

PowerMock è un framework che estende altre librerie di mock come Mockito ed EasyMock ma aggiunge maggiori capacità. PowerMock usa un custom classloader e bytecode manipulation per consentire il mocking dei metodi statici, costruttori, classi final, metodi privati ed altro ancora. Quindi possiamo testare il nostro codice senza modificarlo grazie al fatto che gli oggetti mock vengono agganciati al codice da testare a tempo di esecuzione. Sfruttando la reflection questo framework permette di modificare anche attributi privati della classe.

Ricordiamo che Java permette di costruire applicazioni estendibili dinamicamente, nel senso che un’applicazione è in grado di caricare, a tempo di esecuzione, nuovo codice e di eseguirlo, incluso del codice che nemmeno esisteva quando l’applicazione è stata scritta. Java infatti effettua il caricamento dinamico delle classi, cioè carica le informazioni relative alle classi durante l’esecuzione del programma. Di questo caricamento si occupa un oggetto ClassLoader, una classe messa a disposizione da Java. Un ClassLoader si occupa quindi di importare i dati binari che definiscono le classi (e le interfacce) di un programma in esecuzione.

Configurazione con Android Studio

PowerMock sembra avere uno sviluppo meno veloce di Mockito, quindi non lo possiamo usare insieme all’ultima versione di Mockito. Nulla di insormontabile, ma è importante conoscere questo aspetto per non incorrere in strane eccezioni durante i test. Nella tabella in fig. in basso vengono riportate le versioni di Mockito supportate dalle varie versioni di PowerMock.

Versioni supportate

Nei miei test è stata utilizzata la versione 1.7.0RC2 di PowerMock, quindi il codice da inserire in build.gradle è il seguente:

Le ultime due dipendenze servono per usare PowerMock insieme a JUnit e per sfruttare le API offerte da Mockito versione 2.

Utilizzo di PowerMock

Per esplicitare l’utilizzo di PowerMock insieme a JUnit è possibile utilizzare le annotazioni.

La prima annotazione dice a JUnit di eseguire il testing insieme a PowerMock. La seconda annotazione dice a PowerMock di preparare le classi per il testing. In pratica si indicano le classi per le quali vogliamo manipolare il bytecode. Questa annotazione è richiesta se vogliamo creare mock di classi final o classi con metodi privati, final o static.
Con il codice nella quarta riga esprimiamo la volontà di creare un oggetto mock della classe Button (Android Framework).

In generale nell’annotazione @PrepareForTest va inserita la classe classe da testare, mentre con mock() creiamo l’oggetto mock per simulare una dipendenza della classe sotto test.

Nota: Facciamo attenzione ad importare import static org.powermock.api.mockito.PowerMockito.mock; e non import org.mockito.Mock; per evitare eccezioni a RunTime.

Whitebox

Whitebox è una classe di PowerMock che mette a disposizione diversi metodi statici per bypassare l’incapsulamento. Questa classe sfrutta la reflection di Java. I metodi statici sono i seguenti:

  • Con Whitebox.setInternalState(..) possiamo settare un attributo privato della classe;
  • Con Whitebox.getInternalState(..) possiamo recuperare un oggetto oppure un valore di un attributo della classe;
  • Con Whitebox.invokeMethod(..) possiamo invocare un metodo privato dell’oggetto di una classe;
  • Con Whitebox.invokeConstructor(..) possiamo creare un’istanza di una classe con costruttore privato.

Analizziamo nel dettaglio i primi tre.

Come parametri abbiamo:

  • tested: che è la classe che stiamo testando;
  • nomeattributoclasse: è il nome dell’attributo privato che vogliamo settare;
  • valore: è il valore di quell’attributo della classe, notiamo che se è un semplice int passiamo un valore numerico, mentre se è un’altra classe possiamo passare un mock object di quella classe.

Come parametri abbiamo:

  • tested: che è la classe che stiamo testando;
  • nomeattributoclasse: è il nome dell’attributo privato che vogliamo recuperare;
  • valoreDiRitorno: è il valore di quell’attributo della classe, restituito dal metodo getInternalState(), che può essere un semplice valore intero oppure il riferimento di un oggetto.

Come parametri abbiamo:

  • tested: che è la classe che stiamo testando;
  • metodo: è il nome del metodo privato o protected che vogliamo invocare;
  • param…: possiamo inserire uno o più parametri in base alla firma del metodo.

Mock e Spy

Quando viene usato PowerMock gli oggetti mock e spy devono essere creati in modo diverso rispetto a Mockito.

In quanto a funzionalità sono analoghe a quelle già spiegate per Mockito. Notiamo che Spy invoca la versione reale solo dei metodi implementati in quella classe e non invoca la vera implementazione dei metodi della classe padre, a meno di Override.

PowerMockito Method

In seguito vedremo diversi metodi offerti da PowerMock. Notiamo che esistono dei metodi che sono propriamente di PowerMock, come ad esempio mock(Classe.class). Ma esistono metodi di PowerMock con lo stesso nome dei metodi di Mockito. Questi metodi sono di PowerMockito che è una classe che estende le funzionalità di Mockito con nuove features come ad esempio il mocking di metodi privati o statici. Notiamo che, come consigliato dalla documentazione di PowerMock, anche se i metodi hanno lo stesso nome di Mockito è necessario usare PowerMockito dove applicabile. Questo praticamente avviene facendo attenzione agli import oppure usando esplicitamente PowerMockito.metodo(…)
Analizziamo suppress:

Il metodo suppress consente di sopprimere l’esecuzione di un metodo che si trova all’interno del codice che stiamo testando. Possiamo sopprimere metodi private, protected e public. Notiamo che:

  • method(ClasseProva.class): serve ad indicare in quale classe si trova il metodo da sopprimere;
  • nomeMetodo: è il nome del metodo da sopprimere.

Per verificare che un metodo static sia invocato all’interno del nostro codice possiamo usare verifyStatic:

Notiamo che per far funzionare verifyStatic è importante che l’ordine delle istruzioni sia quello sopraindicato, ovvero prima la riga 1 e poi il metodo static che vogliamo verificare.

Supponiamo di testare un metodo di una ClasseA che eredita dalla ClasseB. Nel caso in cui vogliamo testare in isolamento il metodo della classeA che chiama il costruttore o un metodo della classeB, possiamo sopprimere tutte le chiamate alla classe padre. Il codice è il seguente:

Nel caso in cui vogliamo creare un mock di un metodo statico di una certa classe possiamo fare in questo modo (mockStatic) :

Con:

  • mockStatic: viene indicato a PowerMock la classe con i metodi static;
  • PowerMockito.when: ci permette di creare il fake per il metodo statico metodoStatic().

Notiamo che when è anche un metodo di Mockito, ma non possiamo usare Mockito.when dato che PowerMockito.when è una versione “potenziata” di when che ci permette di trattare metodi statici.

Con PowerMock è possibile intercettare la chiamata del costruttore di una particolare classe in modo da passargli l’oggetto mock o spy creato da noi.

ClasseProva è il nome della classe per la quale vogliamo intercettare il costruttore, con withArguments(..) indichiamo quale costruttore, quindi con quale parametri, deve essere intercettato per far ritornare il nostro oggettoMock.

Vantaggi e svantaggi

Riepilogando, i vantaggi di PowerMock sono:

  • date le sue potenzialità non rende necessario quasi mai modificare il codice sorgente per scopi di testing;
  • fornisce un elevato quantitativo di API per testare classi e metodi pubblic, private, static e final;
  • può essere usato insieme a Mockito ed EasyMock.

Svantaggi:

  • Il suo sviluppo è più lento rispetto a quello di Mockito e quindi non è sempre possibile utilizzare le ultime API di Mockito;
  • La documentazione non è esaustiva e precisa come quella di Mockito;
  • Sono presenti relativamente pochi esempi dell’uso di PowerMock rispetto a Mockito.