Unit Testing – Como mockear un método de una clase padre

Recientemente, en el proyecto en el que estoy, nos hemos encontrado con un problema curioso a la hora de hacer los tests unitarios de una funcionalidad. El método que queríamos testear llamaba a un método de la clase padre, el cual hacia ciertas llamadas a base de datos y diversos recursos externos. No había manera de inicializar esos recursos como mocks antes, por la implementación de la clase padre, y no sabíamos como podíamos mockear solo esa parte, dado que no es otra clase, es la clase padre la que intentamos mockear, lo cual inicialmente no es posible…

Solución (la «regular» pero la más práctica)

Podemos mockear un método de una clase que estemos probando (que el método sea de la clase padre o no, no cambia nada, si es de la clase padre es como si fuera de la actual, lo hereda), usando los Spy de Mockito.

Veamos un poco por encima la diferencia entre Mock y Spy (en el framework Mockito):

  • Mock: cuando Mockito crea un Mock, lo único que hace es crear un esqueleto de la clase, sin funcionalidad alguna. Al llamar a los métodos del mock, no hará nada (aparte del comportamiento que le asignemos con los métodos when, thenReturn, etc)
  • Spy: Al crear un spy, es necesario pasarle un objeto YA creado de esa clase. Y mockito lo que hace realmente es una especie de wrapper sobre ese objeto. Al llamar a los métodos del spy, se ejecutarán los métodos de la clase (a no ser que hayamos cambiado el comportamiento, como vamos a hacer ahora).

Ejemplo:

Pongamos que tenemos estas dos clases. La primera, que tiene un método que usa algún recurso como base de datos o similar, y la segunda que hereda de esta y usa ese método dentro de uno propio:

public class Foo {

    public String doSomethingWithDatabase() {
        System.out.println("Method that connect with DB...");
        try {
            TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("Finished connection whit DB.");

        return "OK real Foo";
    }
}

public class Bar extends Foo {

    public int myMethod() {
        System.out.println("Method to test!");
        String msg = this.doSomethingWithDatabase();
        System.out.println("Msg: ".concat(msg));

        return 1;
    }
}

¿Como testeamos nuestra clase ‘Bar’, mockeando el método de la clase padre? Así:

package com.edwise.pocs.spymockito;

// imports...

public class BarTest {

    private Bar bar;

    @Test
    public void testMyMethodWithMockitoSpy() {
        bar = spy(new Bar());
        doReturn("OK Foo mocked").when(bar).doSomethingWithDatabase();

        int result = bar.myMethod();

        assertThat(result).isEqualTo(1);
    }
}

Creamos un Spy con el método estático de mockito pasándole una instancia ya creada de nuestra clase ‘Bar’. Y ahora, sobre esa instancia hacemos tanto el test (linea 14), como el cambio de comportamiento típico de mockito (linea 12).
Para mockear el método usamos ‘doReturn‘ y no ‘when‘, ya que con when no funcionaría… Con los Spy es necesario usar doReturn (ver javadoc de doReturn para más detalle)

Solución (la correcta…)

Realizar un test como el anterior realmente no es lo más correcto, estamos medio mockeando la clase que estamos probando… Ojo, funciona, y hace lo que tiene que hacer. Pero no es algo muy limpio, mezclamos en la misma instancia la clase testeada y la clase mockeada.
Si necesitamos hacer algo como esto para testear nuestra clase, lo que está pasando es que nuestro diseño no es correcto. En este caso, como en otros muchos, lo que ocurre es que estamos abusando de la herencia, y seguramente la relación entre nuestras dos clases debería ser composición (‘Bar’ no extendería ‘Foo’ y tendría un atributo de tipo ‘Foo’, probablemente pasado en el constructor, o por inyección de dependencias), algo como esto:

public class BarWellDesigned {

    private Foo foo;

    public BarWellDesigned(Foo foo) {
        this.foo = foo;
    }

    public int myMethod() {
        System.out.println("Method to test!");
        String msg = foo.doSomethingWithDatabase();
        System.out.println("Msg: ".concat(msg));

        return 1;
    }
}

La clase Foo sería idéntica a la anterior.

De esta manera nuestro test sería el típico en el que mockeamos la clase de la que dependemos (‘Foo’ en este caso) y testeariamos ‘Bar’ directamente sin ‘wrappearlo’ con el Spy de Mockito.

package com.edwise.pocs.spymockito;

// imports...

public class BarWellDesignedTest {

    private BarWellDesigned barWellDesigned;

    @Test
    public void testMyMethodWithoutMockito() {
        Foo foo = mock(Foo.class);
        barWellDesigned = new BarWellDesigned(foo);
        when(foo.doSomethingWithDatabase()).thenReturn("OK Foo mocked");

        int result = barWellDesigned.myMethod();

        assertThat(result).isEqualTo(1);
    }
}

Pero claro, en el mundo real, cuando te encuentras algo así, hacer un cambio de diseño (o pedir que lo haga el equipo que mantiene ese código), suele ser complicado…

Podéis descargaros todo el código de este post en mi github, proyecto spy-mockito-example.