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());
}

Gradle, Sonar e Informes de cobertura (Jacoco)

Para un proyecto java simple.

El análisis estático de SonarQube se lanza desde Jenkins.

sonar-project.properties:

sonar.projectKey=com.sourcerebels:some_project
sonar.projectName=SomeProject
sonar.projectVersion=1.0
sonar.language=java
sonar.sources=src/main/java
sonar.tests=src/test/java
sonar.java.binaries=build/classes/main
sonar.jacoco.reportPaths=build/jacoco/test.exec

build.gradle

apply plugin: 'java'
apply plugin: 'application'
apply plugin: 'jacoco'

mainClassName = "SomeProject"
sourceCompatibility = 1.8
targetCompatibility = 1.8

repositories {
    jcenter()
    mavenCentral()
}

dependencies {
    testCompile 'junit:junit:4.12'
    testCompile 'org.mockito:mockito-core:2.7.16'
}

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

Configuración Checkstyle Google Checks

Instalar plugin “CheckStyle-IDEA”

  • En Android Studio o IntelliJ abrir la opción de menú “File” -> “Settings”.
  • Allí, localizar el apartado “plugins”.
  • Buscar e instalar el plugin “CheckStyle-IDEA”.
  • Reiniciar el IDE cuando lo solicite.

Configurar Google Checks

  • Volver a abrir la vista de “Settings”.
  • Allí, localizar el apartado “Other Settings -> Checkstyle”.
  • Añadir un nuevo fichero de configuración haciendo click en el botón con el símbolo “+” correspondiente.
  • Seleccionar el checkbox correspondiente para activar la configuración.

Captura de pantalla de 2016-04-09 21-00-04

Finalmente, el fichero se puede localizar on-line en las direcciones.

Fichero para la última versión de checkstyle (no recomendable instalar ya que puede ser que tengas una versión anterior)

https://raw.githubusercontent.com/checkstyle/checkstyle/master/src/main/resources/google_checks.xml

Fichero para una versión en concreto de checkstyle (6.17)

https://github.com/checkstyle/checkstyle/blob/checkstyle-6.17/src/main/resources/google_checks.xml

Las diferentes versiones se pueden encontrar marcadas con tags en el repositorio de Checkstyle.

SonarQube

Para poder analizar un proyecto Android con la herramienta sonarqube tuve que modificar el fichero build.gradle añadiendo las siguientes líneas:

buildscript {
  repositories {
    ...
    maven { url "https://plugins.gradle.org/m2/" } 
  }
  dependencies {
    ...
    classpath "org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:1.2"
  }
}

apply plugin: 'org.sonarqube'

sonarqube {
  properties {
    ...
    property "sonar.projectName", "Mi aplicación"
    property "sonar.host.url", "http://url_de_sonarqube"
    properties["sonar.sources"] += "src"
  }
}