Mockito uso básico

Para hacer TDD es muy importante un framework como mockito, JUnit, para probar comportamientos y resultados sin necesidad de probar el código a grandes rasgos.

El TDD se escriben los test primero antes que escribir una línea de código de producción, escribir los test que testean el código de producción, aunque podría resultar difícil hacer si no se tiene código para compilar, entonces se debe de escribir lo mínimo para una compilación feliz. Un team hace TDD y a la misma vez que obtiene los test hechos, también al mismo tiempo se implementa la funcionalidad.

El agile manifiesto dice “value working code over comprehensive documentation”, con TDD tienes que trabajar con código y probar que el código funcione a la misma vez, si usamos el JavaDocs y TDD, podríamos obtener una linda y buena documentación en paralelo con la entrega de las funcionalidades de los test.

Un código simple

CupCake será nuestra clase a testear en ella usaremos la anotación @InjectMocks

/**
 * Simple CupCake class for @InjectMocks
 */
public class CupCake {

    public static final String INGREDIENT_FOR_CUPCAKE = "Ingredient is: ";

    private final Ingredient ingredient;
    private List<String> ingredients;
    private final SpyMePlease spyMePlease;

    public CupCake(final Ingredient ingredient, final List<String> ingredients, final SpyMePlease spyMePlease) {
        this.ingredient = ingredient;
        this.ingredients = ingredients;
        this.spyMePlease = spyMePlease;
    }

    public String getIngredient() {
        return INGREDIENT_FOR_CUPCAKE.concat(ingredient.getFlavor());
    }

    public int getIngredients() {
        return ingredients.size();
    }

    /**
    *
    * Aquí podemos obtener un resultado diferente al usar @Mock o @Spy
    *
    * @return boolean
    */
    public boolean canISpyThere() {
        System.out.println("Can I Spy there ?");
        spyMePlease.hola();
        return true;
    }

}

Nuestro Mock

La clase Ingredient será nuestro Mock dado que esta es una dependencia de CupCake, todas nuestras dependencias la anotaremos con @Mock, también podríamos usar @Spy para ser inyectadas dentro de los bean anotados con @InjectMocks

/**
 * Simple class Ingredient to @Mock
 */
public class Ingredient {

    private String flavor;

    public Ingredient(final String flavor) {
        this.flavor = flavor;
    }

    public String getFlavor() {
        return flavor;
    }
}
/**
 * Clase a usar con @Spy para inspeccionar funcionalidad
 * interna y externa
 */
public class SpyMePlease {

    public void hola() {
        System.out.println("Your are Spying this!");
    }

}

Con la anotación @Spy podemos probar comportamientos internos y externos de los métodos, sin embargo con @Mock solo los comportamientos externos.

/**
 * En este caso las dependencias como Ingredient e Ingredients serán nuestros mocks anotadas con
 * @Mock donde esta creara las implementaciones que necesitemos.
 *
 * Mientras que nuestra clase CupCake sera la que vamos a testear, anotada con @InjectMock sin necesidad
 * de instanciarla, luego esta inyectara todos los mocks que están marcados con @Mock, @Spy etc
 */
@ExtendWith(MockitoExtension.class)
class MockingAndSpyTest {

    private static final String BLACK_BERRY = "BlackBerry";

    @Mock
    private Ingredient ingredient;

    @Mock
    private SpyMePlease spyMePlease;

    @Spy
    private List<String> ingredients = new ArrayList<>();

    @InjectMocks
    private CupCake cupCake;

    @Test
    void mockAndSpy() {
        Mockito.when(ingredient.getFlavor()).thenReturn(BLACK_BERRY);

        ingredients.add("Banana");
        ingredients.add("Orange");
        ingredients.add("BlueBerry");

        final int actualIngredients = 3;
        assertThat(actualIngredients, is(cupCake.getIngredients()));

        final String actualFlavor = CupCake.INGREDIENT_FOR_CUPCAKE.concat(BLACK_BERRY);
        assertThat(actualFlavor, is(cupCake.getIngredient()));

        assertThat(true, is(cupCake.canISpyThere()));
    }
}

Si ejecutarmos el test el resultado sería GREEN y también el siguiente:

Can I Spy there ?


Process finished with exit code 0

Pero que pasa si cambiamos

@Spy
private SpyMePlease spyMePlease;
Can I Spy there ?
Your are Spying this!


Process finished with exit code 0

Este warning lo tenemos gracias a la anotación @Spy y la versión de openjdk-11

WARNING: An illegal reflective access operation has occurred
WARNING: Illegal reflective access by org.mockito.internal.util.reflection.ReflectionMemberAccessor (file:/C:/Users/Rubn/.m2/repository/org/mockito/mockito-core/3.6.0/mockito-core-3.6.0.jar) to field java.util.ArrayList.elementData
WARNING: Please consider reporting this to the maintainers of org.mockito.internal.util.reflection.ReflectionMemberAccessor
WARNING: Use --illegal-access=warn to enable warnings of further illegal reflective access operations
WARNING: All illegal access operations will be denied in a future release

Se soluciona con 2 dependencias:

  • mockito-core

  • mockito-inline

<!-- https://mvnrepository.com/artifact/org.mockito/mockito-core -->
<dependency>
   <groupId>org.mockito</groupId>
   <artifactId>mockito-core</artifactId>
   <version>3.6.0</version>
   <scope>test</scope>
</dependency>

<!-- https://mvnrepository.com/artifact/org.mockito/mockito-inline -->
<dependency>
   <groupId>org.mockito</groupId>
   <artifactId>mockito-inline</artifactId>
   <version>3.6.0</version>
   <scope>test</scope>
</dependency>