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

Paginación con Spring Data

A modo de recordatorio.

  • El controlador debe recibir un objeto de tipo Pageable
@GetMapping("/myentities")
public Page getMyEntities(final String userUuid, Pageable pageable) {
    return myRepository.findAllByUserUuid(userUuid, pageable);
}
  • Esto significa que a la llamada al controlador se le pueden pasar los parámetros page, limit y sort para controlar la página que queremos recuperar y el orden.
page=número de página a mostrar
limit=número de elementos a mostrar por página
sort=campo por el que se quiere ordenar
  •  Si se quiere personalizar el nombre de estos parámetros, se puede hacer modificando las siguientes propiedades en el fichero application.properties.
spring.data.rest.page-param-name=page
spring.data.rest.limit-param-name=limit
spring.data.rest.sort-param-name=sort
  • El repositorio tiene que extender el interfaz de Spring PagingAndSortingRepository. JpaRepository ya extiende de este.
  • El método findXX del repositorio debe devolver un objeto de tipo Page y recibir un parámetro de tipo Pageable.
@Repository
public interface MyRepository extends JpaRepository<MyEntity, Long> {
    public Page findAllByUserUuid(String userUuid, Pageable pageable);
}

Referencias

https://docs.spring.io/spring-data/rest/docs/1.1.x/reference/html/paging-chapter.html

Saludos

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

Ocultar endpoints Spring Swagger 2

Es posible que en alguna ocasión no queramos que Swagger genere la documentación de algún endpoint por algún motivo concreto. Estos son los pasos a seguir:

1. Definir una anotación con la que decoraremos todos los métodos de los controladores que se quieran ocultar.

import java.lang.annotation.Target;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface HideApiDocumentation {

}

2. Modificar la configuración de Swagger2 para que ignore los métodos anotados.

import static springfox.documentation.builders.PathSelectors.*;
import static com.google.common.base.Predicates.*;
import static springfox.documentation.builders.RequestHandlerSelectors.withMethodAnnotation;

...
...

@Configuration
@EnableSwagger2
public class SwaggerConfiguration {

	@Bean
	public Docket api() {
		return new Docket(DocumentationType.SWAGGER_2)
			.select()
			.apis(not(withMethodAnnotation(HideApiDocumentation.class)))
			.paths(PathSelectors.any())
			.build();
	}
}

3. Utilizar la anotación en los métodos de los controladores

@RestController
public class SomeController {

	@HideApiDocumentation
	@PostMapping("/")
	public String post(@RequestBody String param) {
		...
	}

}

4. Opcional. Ocultar endpoints de librerías de terceros.

Si queremos ocultar también los enpoints generados por librerías de terceros (p.ej oauth), tendremos que indicar manualmente las rutas (método “paths”).

@Bean
public Docket api() {
	return new Docket(DocumentationType.SWAGGER_2)
		.select()
		.apis(not(withMethodAnnotation(HideApiDocumentation.class)))
		.paths(paths())
		.build();
}

private Predicate<String> paths() {
	return not(or(
	regex("/oauth/token.*"),
	regex("/oauth/revoke.*")));
}

Espero que sea de utilidad.

Recomendación SDKMAN

SDKMAN es una herramienta genial que nos permite gestionar diferentes instalaciones de herramientas relacionadas con el mundo Java (JDK, Maven, Gradle, SpringBoot, …) desde la línea de comandos. Lo que es muy práctico es que permite que convivan, de forma muy fácil, diferentes versiones de estas herramientas.

$ sdk current

Using:

gradle: 3.4.1
grails: 3.2.7
groovy: 2.4.10
kotlin: 1.1.1
maven: 3.3.9
sbt: 0.13.13
scala: 2.12.1
springboot: 1.5.2.RELEASE

Si no lo estáis usando ya, os recomiendo que le echéis un ojo.

Saludos

No funciona el login JHipster Oauth 2

Al crear una aplicación con JHipster, habilitando la autenticación con Oauth2, me he encontrado que no era capaz de entrar en la app generada con los usuarios por defecto (admin y user).

El “problema”, si no he entendido mal, es que jhipster está generando incorrectamente un fichero .csv, que posteriormente cargará en la BBDD.

src/main/resources/config/liquibase/oauth_client_details.csv

Este fichero tenía la siguiente pinta:

client_id;resource_ids;client_secret;scope;authorized_grant_types;web_server_redirect_uri;authorities;access_token_validity;refresh_token_validity;additional_information;autoapprove
<%= baseName %>app;res_<%= baseName %>;my-secret-token-to-change-in-production;read,write;password,refresh_token,authorization_code,implicit;;ROLE_ADMIN,ROLE_USER;1800;2000;{};true

Se trata de sustituir el tag: <%= baseName %> por el nombre de tu aplicación. Suponiendo que tu app se llame “example”, quedaría así:

client_id;resource_ids;client_secret;scope;authorized_grant_types;web_server_redirect_uri;authorities;access_token_validity;refresh_token_validity;additional_information;autoapprove
exampleapp;res_example;my-secret-token-to-change-in-production;read,write;password,refresh_token,authorization_code,implicit;;ROLE_ADMIN,ROLE_USER;1800;2000;{};true

Una vez hecho esto, rearrancamos la app y al volver a cargar los datos de la BD ya tiene las referencias correctas y ya es posible hacer login.

Realmente no tengo muy claro si esto es un bug de JHipster o símplemente es que no he seguido todos los pasos necesarios para configurar la instalación.

Bonus: Para visualizar el contenido de la BD h2 en memoria, es posible utilizar el cliente web al que podemos acceder en la siguiente dirección: http://localhost:8080/h2-console.

Saludos y espero que le sirva a alguien

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'
}

JitPack.io mola mucho

Hace ya un tiempo que vengo siguiendo este servicio (he visto que mucha gente lo va utilizando). No voy explicar nada del otro mundo, simplemente quiero recomendar su uso.

JitPack.io es un servicio que permite publicar un tag concreto de un repositorio de Github en su repositorio de Maven.

El esfuerzo necesario para publicar i/o utilizar una librería de Android es tan poco que parece mentira.

Este repositorio es compatible con los sistemas de construcción: Maven (Java), Gradle (Java, Android, Groovy), SBT (Scala) o Leiningen (Clojure).

Mola mucho. Buen trabajo 🙂