jmockit logo

Testing con JMockit

Visión general

JMockit es un potente framework de testing en Java para simular objetos, lo que se conoce como mocking. Su ventaja es que utiliza las API de instrumentación de Java para modificar el bytecode de las clases durante el tiempo de ejecución para alterar dinámicamente su comportamiento. Otros de sus puntos fuertes son su expresibilidad y su capacidad extraordinaria para simular métodos estáticos y privados. En esta guía analizaremos dicha funcionalidad y lo compararemos con otras soluciones, justificando su elección como la herramienta más completa para mocking.

¿Qué es el Mocking?

Un Mock es una clase utilizada para simular el comportamiento de otra u otras clases más complejas, por ejemplo un servicio de bases de datos y sus dependencias asociadas.

Ejemplo de mocking de un servicio de base de datos

Estas dependencias y comportamientos se pueden simular de dos maneras:

  • programándolos a mano, creando todo el conjunto de clases, interfaces y propiedades con los valores necesarios para el test.
  • sirviéndonos de un framework de mocking para la creación de los test, sin tener que crear nuevas clases que aumenten la complejidad de nuestro código ni modificar el comportamiento de las existentes.

¿Por qué utilizar mocking?

Un objeto mock representa una clase o interface simulada con una respuesta preestablecida. El código real de la clase simulada se omite para reducir el esfuerzo de inicialización a la hora de realizar el conjunto de pruebas.

Las principales ventajas del mocking en los test unitarios son las siguientes:

  • Testa las clases o los métodos por separado.
  • No es necesario escribir pruebas para código heredado (legacy code).
  • Ignora las dependencias de otras clases que no son relevantes al test.
  • Elimina las dependencias de librerías externas y servidores (bases de datos, servicios web, sockets, etc..).
  • Simplifica el entorno de prueba al utilizar clases «dummy» en vez de unidades más complejas.

Introducción a JMockit

¿Qué es JMockit?

JMockit es una librería de testing para Java, con una amplia colección de herramientas y bajo licencia opensource, para el mocking de objetos en los entornos de pruebas. Se utiliza conjuntamente con las librerías de testing JUnit o TestNG.

¿Por qué JMockit?

La razón más importante para optar a realizar los tests con JMockit es porque, básicamente, nos permite realizar un mock de cualquier cosa. Y recalcamos, literalmente cualquier cosa.

Comparativa entre las principales librerías de mocking

A pesar de ser inicialmente un poco más complejo que otras librerías, sin duda vale la pena decantarse por esta librería debido a la potencia y facilidad que nos brinda respecto a otras herramientas para el mocking.

JMockit vs PowerMock

Como vemos, JMockit y PowerMock no tienen limitaciones a la hora de realizar un mock de un objeto, por lo que, dejando de lado las otras herramientas de mocking, mucho más simples pero con más limitaciones, pasaremos a ver las diferencias entre ambas librerías.

  • PowerMock no proporciona una API completa para mocking, sino que funciona como una extensión de otra herramienta, que puede ser EasyMock o Mockito. Esto es útil para los usuarios que conozcan estas herramientas pero confunde al mezclar funcionalidades.
  • JMockit, por otro lado, proporciona una API completamente nueva. Si bien esto crea una curva de aprendizaje más larga, también permite que JMockit proporcione una API más simple, más consistente y más fácil de usar.
  • En comparación con la API de expectativas (Expectations) de JMockit, la API de PowerMock es más «de bajo nivel», lo que obliga a los usuarios a averiguar y especificar qué clases deben prepararse para la prueba (con la anotación @PrepareForTest ({ClassA.class, …}) ) y requiere llamadas API específicas para tratar con varios tipos de construcciones de lenguaje que pueden estar presentes en el código de producción: métodos estáticos (mockStatic (ClassA.class)), constructores (suppress (constructor (ClassXyz.class))), invocaciones de constructor ( expectNew (AClass.class)), simulacros parciales (createPartialMock (ClassX.class, "methodToMock")), etc.
  • Con las expectativas de JMockit, todos los tipos de métodos y constructores se mockean de manera puramente declarativa, con mocks parciales especificados a través de expresiones regulares en la anotación @Mocked o simplemente realizando un «un-mocking» a los miembros sin expectativas registradas; es decir, el desarrollador simplemente declara algunos «campos mockeados» compartidos para la clase de prueba, o algunos «campos mockeados locales» y / o «parámetros mockeados» para métodos de prueba individuales (en este último caso, la anotación @Mocked a menudo no será necesaria).
  • Algunas capacidades disponibles en JMockit son la posibilidad de mockear los métodos equals y hashCode, los métodos marcados con @Override y otros aspectos no están disponibles en PowerMock. Además, no hay un equivalente a la capacidad de JMockit para capturar instancias y simular implementaciones de tipos de base específicos a medida que se ejecuta la prueba, sin que el código de prueba tenga conocimiento de las clases de implementación reales.
  • PowerMock usa cargadores de clases (class loader) personalizados (generalmente uno por clase de prueba) para generar versiones modificadas de las clases simuladas. Este uso intensivo de cargadores de clases personalizados puede llevar a conflictos con bibliotecas de terceros, por lo tanto, muchas veces es necesario usar la anotación @PowerMockIgnore ("package.to.be.ignored") en las clases de prueba.
  • El mecanismo utilizado por JMockit (instrumentación de tiempo de ejecución a través de un «agente Java») es más simple y seguro, aunque requiere pasar un parámetro «-javaagent» a la JVM cuando se desarrolla en JDK 1.5; en JDK 1.6+ (que siempre se use para el desarrollo), no existe tal requisito, ya que JMockit puede cargar el agente de Java de forma transparente mediante la API Attach.

Comenzando con JMockit

JMockit nos proporciona una API para el mock de:

  • Métodos públicos y privados
  • Parámetros y constructores
  • Métodos y bloques estáticos

Los test realizados con JMockit se dividen en tres fases diferentes: grabación, reproducción y verificación, según el modelo Record-Replay-Verify.

  1. En la fase de grabación, durante la preparación de la prueba y antes de las invocaciones a los métodos que queremos ejecutar, definiremos el comportamiento esperado para todas las pruebas que se utilizarán durante la siguiente etapa.
  2. La fase de reproducción es aquella en la que se ejecuta el código bajo prueba. Se reproducirán las invocaciones de métodos/constructores mockeados previamente grabados en la etapa anterior.
  3. Por último, en la fase de verificación, confirmaremos que el resultado de la prueba fue el que esperábamos (y que los mocks se comportaron de acuerdo con lo que se definió en la fase de registro).
@Test
public void testClass() {
   // preparación de código ajeno a JMockit, en caso de haberlo
 
   new Expectations() {{ 
       // definimos el comportamiento de los mocks
   }};
 
   // se ejecuta el código probado
 
   new Verifications() {{ 
       // verificamos los resultados
   }};
 
   // bloque de asserts de JUnit
}

La forma trabajar con JMockit es mediante su conjunto de anotaciones. A continuación veremos las más comunes.

@Tested: Esta anotación creará un objeto inicializado de la clase que queremos probar. Se intentará satisfacer todas sus dependencias internas mediante las anotaciones de JMockit, si todavía no se satisfacen, intentará crear una instancia de las dependencias, y si eso falla, serán nulas.

@Mocked: Esta anotación creará instancias totalmente mockeadas de la clase (incluyendo estáticos y constructores, ya que se reemplaza todo el bytecode). Cada nueva instancia de la clase será un mock y no una instancia real de la clase.

@Capturing: Se comportará como @Mocked, pero extenderá su alcance a cada subclase que extienda o implemente el tipo de campo anotado. Esto también aplica a las interfaces.

@Injectable: Solo se creará una instancia mockeada del tipo requerido. Puede usarse para simular el comportamiento, la verificación y la inyección en la clase bajo prueba. Será capaz de mockear todas las cosas no estáticas, incluyendo todo lo marcado con final.

Al disponer de un poder absoluto a la hora de realizar un mock debemos ser muy consientes de lo que ofrece cada nivel de mockeo. Si solo usáramos @Mocked cualquier instancia del tipo marcado se mockeará. Además no se tendría en cuenta los casos particulares, todo será tratado de la misma forma. @Injectable maneja instancias, pero hay veces que necesitamos más control, por tanto se introduce el alcance (scope). Al utilizar anotaciones en los parámetros de prueba, en vez de aplicarlos a los campos, podemos jugar con el alcance. Por tanto, podremos elegir si durante una prueba el comportamiento deseado será mockear el parámetro para una prueba en particular o elegir el campo y mockear toda la clase, la clase y subclases o solo una instancia en particular.

David Hardy – endrand.nl

A priori cuesta diferenciar el uso de @Mocked y @Injectable, la forma más fácil de hacerlo es:

  1. @Mocked mockea absolutamente todo, incluyendo todas las instancias de esa clase, y @Injectable solo mockea un método/campo específico de una instancia de esa clase.
  2. @Injectable se utiliza siempre cuando se haya realizado una inyección explícita de dependencias (DI), de lo contrario se obtendrá:
    java.lang.IllegalArgumentException: No constructor in tested class that can be satisfied by available tested/injectable values.

Para ilustrar este artículo, incluiremos un ejemplo completo de una prueba utilizando JMockit. Fuente baeldung.com.

En este ejemplo, probaremos una clase Performer que usa Collaborator en su método perform (). Este método perform () recibe un objeto Model como un parámetro desde el cual usará su método getInfo () que devuelve una cadena, esta cadena pasará al método collaborate () de Collaborator que devolverá verdadero para esta prueba en particular, y este valor se pasará al método receive () de Collaborator.

Las clases a probar son las siguientes:

public class Model {
    public String getInfo(){
        return "info";
    }
}
 
public class Collaborator {
    public boolean collaborate(String string){
        return false;
    }
    public void receive(boolean bool){
        // NOOP
    }
}
 
public class Performer {
    private Collaborator collaborator;
     
    public void perform(Model model) {
        boolean value = collaborator.collaborate(model.getInfo());
        collaborator.receive(value);
    }
}

Por lo que el código de testing con JMockit queda:

public class PerformerTest {
 
    @Injectable
    private Collaborator collaborator;
 
    @Tested
    private Performer performer;
 
    @Test
    public void testThePerformMethod(@Mocked Model model) {
        new Expectations() {{
            model.getInfo();result = "bar";
            collaborator.collaborate("bar"); result = true;
        }};
        performer.perform(model);
        new Verifications() {{
            collaborator.receive(true);
        }};
    }
}

Conclusiones

Al trabajar por primera vez, JMockit parece tener una sintaxis extraña pero no toma mucho tiempo acostumbrarse. Después del susto inicial, sigue siendo muy consistente, sin importar qué tipo de prueba esté realizando.

Los bloques de expectativas (Expectations) y verificaciones (Verifications) mantienen siempre la misma forma. Es increíblemente fácil escribir mocks para enums, métodos estáticos, nuevas instancias, etc. JMockit cubre todo lo necesario y realmente ayuda a centrarse en escribir las pruebas debido a su sintaxis, realmente simple.

El mayor argumento a favor de JMockit es la facilidad de uso y la forma en que ayuda en lugar de obstaculizar. Hay frameworks que cuestan mucho más tiempo para dominar adecuadamente y ofrecen menos funcionalidades.

Si te ha convencido la herramienta por favor deja tu comentario o valoración. También puedes ver el tutorial completo donde se explica su integración al proyecto mediante Maven y se analiza su uso y capacidades en forma de tutorial con ejemplos prácticos.

Valoración: 1 estrella2 estrellas3 estrellas4 estrellas5 estrellas (1 votos, promedio: 5,00 de 5)
Cargando…

Deja tu comentario...