Mejorar la salida de los tests de una aplicación Spring Boot

Cuando hago aplicaciones con SpringBoot abruma un poco la cantidad de información que este muestra en el log cuando está activado el debug.

El tema es que el nivel de log lo suelo configurar a través del fichero application.properties pero cuando se ejecutan tests de JUnit donde no interviene Spring para nada este valor no se recoje y por defecto muestra todo a nivel DEBUG.

Añadiendo este fichero logback-test.xml en la ruta src/test/resources conseguiremos que los mensajes del framework Spring se muestren con nivel WARN mientras que los del paquete de mi aplicación (en el ejemplo: com.my.package) se muestren con el nivel más alto TRACE.

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <include resource="org/springframework/boot/logging/logback/base.xml" />
    <logger name="org.springframework" level="WARN"/>
    <logger name="com.my.package level="TRACE"/>
</configuration>

De esta forma la salida (standard output) de los tests queda muchísimo más limpia y prácticamente se limita a los mensajes própios de mi aplicación.

Failed to instantiate Pageable: Specified class is an interface

Al añadir los parámetros de paginación a un controlador de spring. Unos tests que estaba haciendo con MockMvc y Mockito me comenzaron a fallar con el siguiente error:

Caused by: org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.springframework.data.domain.Pageable]: Specified class is an interface

La solución consiste en proporcionar una clase que resuelva los argumentos en tiempo de test (setCustomArgumentResolvers). Esta clase solo atiende al parámetro de tipo Pageable.

public class TestUtils {
    public static MockMvcBuilder prepareMockMvcForPageableArguments(Object... controllers) {
        return MockMvcBuilders.standaloneSetup(controllers)
                .setCustomArgumentResolvers(
                        new HandlerMethodArgumentResolver() {
                            @Override
                            public boolean supportsParameter(MethodParameter parameter) {
                                return parameter.getParameterType().equals(Pageable.class);
                            }

                            @Override
                            public Object resolveArgument(MethodParameter parameter,
                                    ModelAndViewContainer container,
                                    NativeWebRequest request,
                                    WebDataBinderFactory binderFactory) throws Exception {
                                return new PageRequest(0, 50);
                            }
                        });

    }
}

Para utilizarlo:

MockMvc mvc = TestUtils.prepareMockMvcForPageableArguments(controller).build();

Fuente:

https://github.com/terasolunaorg/terasoluna-tourreservation/blob/master/terasoluna-tourreservation-web/src/test/java/org/terasoluna/tourreservation/app/searchtour/SearchTourControllerTest.java

Mockito InOrder y ArgumentCaptor

InOrder

Uso de InOrder permite validar el orden en el que se ejecutan las llamadas a los distintos objetos doble (mock).

@Mock
Repository someRepository;

@Mock
Repository someOtherRepository;

@Test
public void testSomething() {

	InOrder order = Mockito.inOrder(someRepository, someOtherRepository);

	order.verify(someRepository).save(anyString());
	order.verify(someOtherRepository).findByName(anyString());
	order.verify(repository).update(anyString());
	order.verifyNoMoreInteractions();
}

ArgumentCaptor

Uso de ArgumentCaptor permite capturar los argumentos que recibe una llamada a una objeto doble (mock).

@Mock
Repository mockRepository;

@Test
public void testSomething() {

	service.useCase("some_string");
	
	ArgumentCaptor<String> argument = ArgumentCaptor.forClass(String.class);
	verify(mockRepository).findByName(argument.capture());
	assertEquals("some_string", argument.getValue());
}

Mostrar el resultado de los test unitarios al ejecutar Gradle

Una de las cosas que me confunde cuando ejecuto el Gradle desde la línea de comandos es que si los tests se ejecutan sin fallos, no muestra ningún tipo de detalle acerca de que tests fueron ejecutados. Con lo que no acabo de tener la certeza de que todo haya ido bien.

Al añadir estas líneas en el fichero build.gradle de nuestro módulo mostrará la información de qué test ha ejecutado y cual ha sido el resultado, además de la salida standard y error si la hubiese.

android {
  ...
  testOptions.unitTests.all {
    ignoreFailures false
    testLogging {
      events "passed", "skipped", "failed", "standardOut", "standardError"
    }
  }
}

Por supuesto, yo no he inventado nada. Lo saqué de esta respuesta en Stackoverflow.

Tema relacionado: Test from the Command Line