Home > Desarrollo, Librerías/Frameworks, Testing > Mockear métodos estáticos, privados, finales y mas con PowerMock

Mockear métodos estáticos, privados, finales y mas con PowerMock

La inyección de mocks o stubs en tests unitarios es una técnica muy extendida, debido a que  permite eliminar las dependencias externas de las clases a testear, con lo que se simplifica enormemente la creación del entorno de test. Existen varias herramientas, como Mockito, que facilitan enormemente esta tarea al proporcionar métodos sencillos de creación (y verificación) de mocks y stubs. Otra opción que goza de gran aceptación es EasyMock.

Pero, en cuanto a la inyección de dependencias, tanto si se realiza la inyección directamente mediante constructores o setters, o se usan herramientas específicas como, por ejemplo, Guice, en muchas ocasiones aparece el problema de que el diseño apropiado no es el mas adecuado para la inyección de mocks: si para crear un mock de un método estático habría que crear algún tipo de wapper o adaptador, tampoco se pueden usar mocks para métodos privados, a menos que se cambiase la visibilidad a protected y se corriesen los tests contra una clase que, extendiendo de la clase a testear, proporcionase métodos públicos que, de esa manera, permitiesen acceso a los protegidos de la clase base. Pero, en ambos casos, se requiere modificar el código y, además, no se prueba exactamente la clase que se desea.

Estos son algunos de los motivos por los que puede ser útil dedicar algo de tiempo a trastear con PowerMock. Otra razón de peso es que para usar PowerMock no es necesario modificar los tests existentes: PowerMock está diseñado para extender Mockito y EasyMock, en lugar de para reemplazarlos. Como entornos de test soporta JUnit y TestNG. A continuación se muestra una pueba de concepto usando PowerMock para extender Mockito en un entorno de test JUnit4.

Se parte del supuesto de que se desea testear un código que se acaba de escribir y que usa un método estático de otra clase. Si el método no fuese estático, se podría, simplemente, inyectar un mock de la clase de la que se depende sin mas problemas.

La clase con la que se mantiene la dependencia:

public class LegacyCode {
public static String getMessage(){
 return new String("Esto es una castaña (estática)");
 }
public String getAnotherMessage(){
 return new String("Esto es una castaña");
 }
}

La clase que se desea testear:

public class MyClass {
public String methodToTest (){
 return LegacyCode.getMessage();
 }
public String methodToTestNotStatic(LegacyCode legacyCode){
 return legacyCode.getAnotherMessage();
 }
}

Y el test:

public class MyClassTest {
 @Test
 public void testLegacyCode(){
 // Creamos un mock para LegacyCode usando Mockito
 LegacyCode mockLegacyCode = mock(LegacyCode.class);
 // Le indicamos lo que debe devolver en este test en concreto
 when(mockLegacyCode.getAnotherMessage()).thenReturn("Lo que diga el mock");
// Instanciamos y comprobamos el mensaje devuelto
 MyClass myClass = new MyClass();
 System.out.println(myClass.methodToTestNotStatic(mockLegacyCode));
 verify(mockLegacyCode, times(1)).getAnotherMessage();
 }
 }

En realidad, testear no se testea nada, el objetivo es simplemente ver que el mock funciona. Y, en efecto, al ejecutar el test se obtiene la cadena Lo que diga el mock  en lugar de Esto es una castaña.

Nada nuevo hasta aquí para los que ya usan estas técnicas. Pero, ¿ que ocurre al testear el método methodToTest de la clase ? En este caso, al llamar a un método estático, no se puede inyectar un mock tal y como se acaba de ver.

Aquí es donde entra PowerMock. Se puede usar PowerMock para crear el siguiente test, donde en este caso se incluyen los imports para que se pueda apreciar como PowerMock (PowerMockito en este caso) tan sólo extiende, pero no sustituye, a Mockito:

 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.powermock.core.classloader.annotations.PrepareForTest;
 import org.powermock.modules.junit4.PowerMockRunner;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 import static org.powermock.api.mockito.PowerMockito.mockStatic;
 @RunWith(PowerMockRunner.class)
 @PrepareForTest(LegacyCode.class)
 public class MyClassTest {
 @Test
 public void testLegacyCode(){
 MyClass myClass = new MyClass();
 LegacyCode mockLegacyCode = mock(LegacyCode.class);
 when(mockLegacyCode.getAnotherMessage()).thenReturn("Lo que diga el mock");
 System.out.println(myClass.methodToTestNotStatic(mockLegacyCode));
 verify(mockLegacyCode, times(1)).getAnotherMessage();
 }
 @Test
 public void testMockLegacyCode(){
 mockStatic(LegacyCode.class);
 when(LegacyCode.getMessage()).thenReturn("Esto ya es otra cosa");
 MyClass myClass = new MyClass();
 System.out.println(myClass.methodToTest());
 }
 }

Nota: aunque no aparezca a simple vista en la documentación de PowerMock, es necesario incluir en el classpath javassist.

En este caso se ha creado un mock para la clase completa, aunque tambien podría crearse un mock parcial, sólo para el método estático.

La anotación @RunWith debe añadirse siempre para usar PowerMock. Echando un vistazo puede apreciarse que se ha escogido el PowerMockRunner para JUnit 4.

Para crear mocks de clases con métodos estáticos, debe añadirse además la anotación @PrepareForTest, indicando la clase para la que se creará el mock, en este caso LegacyCode.class. Por lo demás, la única diferencia es que, en lugar de llamar al método mock de Mockito, se llama al mockStatic de PowerMockito.

Ejecutando los tests se obtiene como salida:

Lo que diga el mock
Esto ya es otra cosa

Lo que diga el mock, en lugar de Esto es una castaña, como ya se había visto anteriormente, y Esto ya es otra cosa, en lugar de Esto es una castaña (estática), que es lo que devuelve el método estático.

En resumen, PowerMock añade a Mockito o EasyMock, usados conjuntamente con JUnit o TestNG, la posibilidad de crear mocks para métodos estáticos (clase completa o sólo métodos), métodos privados e incluso constructores. De hecho, la posibilidad de crear mocks para métodos estáticos y constructores roza el terreno de la inyección de dependencias en sí mismo.

 

Advertisements
  1. jbescos
    01/10/2012 at 09:36

    Interesante articulo. Lo cierto es que tanto thinking patters, thinking objects, thinking tests, thinking aspects, thinking etc… al final satisfacer todas las buenas prácticas resulta dificil y te tienes que decantar mas por una que por otra. En el caso de los mocks, ¿quién no se ha visto obligado alguna vez a cambiar la visibilidad de un método?, o ¿quién no se ha encontrado métodos o constructores en clases que solo tienen sentido para el testéo?. Parece que con PowerMock te despreocupas mas de pensar en como testear para centrarte sólo en hacer un buen código.

  2. jbescos
    01/30/2012 at 18:00

    Hemos estado probando el PowerMockito y hemos tenido un problema que parece bastante comun, asi que esta bien dejar la solucion.

    Nos estaban saliendo errores en el log como este:
    log4j:ERROR A “org.apache.log4j.ConsoleAppender” object is not assignable to a “org.apache.log4j.Appender” variable.

    En consecuencia no nos pintaba ninguna de nuestras trazas.

    Al principio hemos pensado que como la clase estatica que queriamos mockear contenia un LogFactory statico, estaba intentando mockearlo dando el error anterior. Lo sorprendente es que eliminando ese LogFactory ha seguido dando el mismo error.

    Sin saber si esto es un bug o no, podemos evitar ese error poniendo la anotacion @PowerMockIgnore({“org.apache.log4j.*”}) en la cabecera de la clase que va a testear la clase estatica. Asi nuestros logs (salvo los que esten dentro de la clase a mockear) funcionaran.

  1. 01/09/2012 at 16:23

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: