lunes, octubre 23, 2017

Powermock: Notas rápidas para interceptar (mock) llamadas estáticas

Powermock es una librería que añade nuevas capacidades de test a algunas librerías de test, usando su propio cargador de clases y reescribiendo el bytecode de las clases que están siendo probadas. De esta manera se pueden probar métodos estáticos, probar clases saltándose bloques de inicialización estáticos o acceder al estado interno de un objeto. Por defecto extienden las funcionalidades de mockito y EasyTest

Si estamos usando maven, se puede añadir una referencia a powermock automáticamente para que se baje de los repositorios que se tenga configurado. Si se está usando Mockito 1.x y JUnit 4.4 se puede usar la siguiente configuración en las dependencias del pom. Si se consulta en enlace, veréis que es diferente dependiendo de si es JUnit 4.4 o una versión anterior. Notar que el uso de JUnit 3.x está desaconsejado por obsoleto. Una vez añadido a nuestro pom.xml y maven está configurado correctamente, se bajará las dependencias necesarias.

<properties>
    <powermock.version>1.7.1</powermock.version>
</properties>
<dependencies>
   <dependency>
      <groupId>org.powermock</groupId>
      <artifactId>powermock-module-junit4</artifactId>
      <version>${powermock.version}</version>
      <scope>test</scope>
   </dependency>
   <dependency>
      <groupId>org.powermock</groupId>
      <artifactId>powermock-api-mockito</artifactId>
      <version>${powermock.version}</version>
      <scope>test</scope>
   </dependency>
</dependencies>

Puesto que uso JUnit 4 junto con PowerMock, es necesario configurar la ejecución de los test para que haga uso de la infraestructura de Powermock cuando se ejecuten. Eso se consigue anotando la clase que implementa los test con @RunWith(PowerMockRunner.class). Además, para que Powermock sepa qué clases debe de preparar, es necesario especificar con otra anotación qué clases deben de transformarse. Esta anotación es @PrepareForTest. Se ve mejor con el siguiente ejemplo. Supongamos que queremos verificar la funcionalidad de la siguiente clase que sólo tiene dos métodos estáticos:

package org.menzoberrazan.maven;
public class StaticClass {
    static public String metodo1(){
        return "Método 1 llamado";
    }
    static public String metodo2(){
        return "Método 2 llamado";
    }
}

La clase que se va usar para probar está al final del párrafo. En ella hay tres tests:

  • Un primer test normal, que nos muestra lo que devolvería los métodos estáticos son mocks
  • Un segundo test, donde se mockea toda la clase, por lo tanto hay que dar en el test una implementación de cada método que se llame, cosa que se hace con when
  • Un último test, donde vemos un mock parcial de uno de los métodos de la clase estática.
package org.menzoberrazan.maven;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.powermock.api.mockito.PowerMockito;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;

import static org.junit.Assert.assertEquals;

@RunWith(PowerMockRunner.class)
@PrepareForTest({StaticClass.class})
public class AppTest
{
    @Test
    public void test1(){
        assertEquals("Método 1 llamado",StaticClass.metodo1());
        assertEquals("Método 2 llamado", StaticClass.metodo2());
    }
    @Test
    public void test2(){
        PowerMockito.mockStatic(StaticClass.class);
        PowerMockito.when(StaticClass.metodo1()).thenReturn("Método 1 mockeado");
        PowerMockito.when(StaticClass.metodo2()).thenReturn("Método 2 mockeado");
        assertEquals("Método 1 mockeado", StaticClass.metodo1());
        assertEquals("Método 2 mockeado", StaticClass.metodo2());
    }
    @Test
    public void test3(){
        PowerMockito.stub(PowerMockito.method(StaticClass.class, "metodo1")).toReturn("Método 1 parcial");
        assertEquals("Método 1 parcial", StaticClass.metodo1());
        assertEquals("Método 2 llamado", StaticClass.metodo2());
    }
}

Los puntos claves a tener en cuenta en este ejemplo son:

  • Las anotaciones en la clase que implementa los test.
  • Las anotaciones que se usan para indicar que un determinado método es un test (@Test)
  • El uso de las diferentes funciones assert* que nos provee junit para poder comparar los valores.
  • El mock total o parcial de la clase.
  • El uso de las versiones correctas de los paquetes en el pom.xml

No hay comentarios: