Documentar un servicio REST, con Swagger 2 (SpringFox)

Hace bastante tiempo publiqué un post en el que explicaba como documentar un servicio REST montado con Spring, con Swagger. Desde entonces, la librería usada para Spring ha cambiado bastante (incluso de nombre y de dueños…). En este post veremos como hacerlo con la nueva librería, SpringFox.

Concepto

Desde hace tiempo la antigua librería ‘swagger-springmvc‘, que servia para integrar swagger fácilmente en un proyecto Spring, ha pasado a llamarse SpringFox, y ha cambiado un poco. En sus últimas versiones soporta Swagger 2.0. Ya hablé un poco sobre Swagger en el post que comentaba antes, pero tenéis más info sobre swagger y sobre SpringFox en estos links:
http://swagger.io/
https://github.com/swagger-api
http://springfox.github.io/springfox/
https://github.com/springfox/springfox

En este post voy a documentar un servicio REST Spring con Swagger 2.0, usando SpringFox. Para ello usare como base un proyecto SpringBoot con un servicio REST ‘Infos’ que ya use en otros posts anteriores:
https://anotherdayanotherbug.wordpress.com/2015/03/16/tests-de-integracion-para-un-servicio-rest-con-spring/
https://anotherdayanotherbug.wordpress.com/2015/05/25/tests-de-integracion-usando-rest-assured/

Entorno usado:
Java JDK 1.8
Maven 3.3.9
Git 2.6.3
IDE Intellij 15.0.2 Ultimate version

Pasos

1. Creamos nuestro proyecto SpringBoot como siempre , con la versión 1.3. Lo principal que necesitamos es el pom.xml de Maven, la clase ‘main’ de SpringBoot … y listo.

2. Implementamos nuestro sencillo servicio REST. Un controller, que usa un Service, él cual usa un Repository (mockeado este último). Nuestra entity y el controller son estos:

public class Info {

    private Long id;
    private String infoText;
    private LocalDateTime creationDateTime;

    // getters and setters
}

@RestController
@RequestMapping("/api/infos")
public class InfoController {

    @Autowired
    private InfoService infoService;

    @ResponseStatus(HttpStatus.OK)
    @RequestMapping(method = RequestMethod.GET,
            produces = MediaType.APPLICATION_JSON_VALUE)
    public List<Info> getAllInfos() {
        return infoService.findAll();
    }

    @ResponseStatus(HttpStatus.OK)
    @RequestMapping(method = RequestMethod.GET, value = "{id}",
            produces = MediaType.APPLICATION_JSON_VALUE)
    public Info getInfo(@PathVariable Long id) {
        return infoService.findOne(id);
    }

    @RequestMapping(method = RequestMethod.POST,
            produces = MediaType.APPLICATION_JSON_VALUE)
    public ResponseEntity<Info> createInfo(@RequestBody Info info) {
        Info infoCreated = infoService.save(info);
        return new ResponseEntity<>(createHeadersWithLocation(infoCreated),
                HttpStatus.CREATED);
    }

    @ResponseStatus(HttpStatus.NO_CONTENT)
    @RequestMapping(method = RequestMethod.PUT, value = "{id}")
    public void updateInfo(@PathVariable Long id, @RequestBody Info info) {
        infoService.update(info.setId(id));
    }

    @ResponseStatus(HttpStatus.NO_CONTENT)
    @RequestMapping(method = RequestMethod.DELETE, value = "{id}")
    public void deleteInfo(@PathVariable Long id) {
        infoService.delete(id);
    }

    private HttpHeaders createHeadersWithLocation(Info info) {
        HttpHeaders httpHeaders = new HttpHeaders();
        httpHeaders.setLocation(
                ServletUriComponentsBuilder
                        .fromCurrentRequest()
                        .path("/{id}")
                        .buildAndExpand(info.getId())
                        .toUri());
        return httpHeaders;
    }
}

3. Vamos a empezar a integrarle Swagger 2 con SpringFox. Primero añadimos las librerías necesarias a nuestro pom.xml:

...
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger-ui</artifactId>
            <version>2.3.0</version>
        </dependency>
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger2</artifactId>
            <version>2.3.0</version>
        </dependency>
...

La principal que necesitamos es ‘springfox-swagger2‘.
La de ui es más bien un ‘webjar’ con la ui de swagger de siempre. Lo bueno es que esta todo en el jar, y no tenemos que bajarnos toda la ui en una subcarpeta del proyecto, como hice en el post anterior.

4. Creamos una clase de configuración de Spring, con la anotación @EnableSwagger2 y con el bean básico que necesita Swagger 2 (Docket):

package com.edwise.pocs.swagger2.config;

// imports...

@Configuration
@EnableSwagger2
public class SwaggerConfig {

    @Bean
    public Docket newsApi() {
        return new Docket(DocumentationType.SWAGGER_2)
                .groupName("api-infos")
                .apiInfo(apiInfo())
                .directModelSubstitute(LocalDateTime.class, Date.class)
                .select()
                .paths(regex("/api.*"))
                .build();
    }

    private ApiInfo apiInfo() {
        return new ApiInfoBuilder()
                .title("Infos REST api")
                .description("PoC of a REST api, Infos")
                .termsOfServiceUrl("http://en.wikipedia.org/wiki/Terms_of_service")
                .contact("edwise.null@gmail.com")
                .license("Apache License Version 2.0")
                .licenseUrl("http://www.apache.org/licenses/LICENSE-2.0.html")
                .version("2.0")
                .build();
    }
}

Esto es similar a la anterior configuración con la librería swagger-springmvc, pero aquí en lugar de un SwaggerSpringMvcPlugin, tenemos un bean llamado Docket, pero es similar. Tiene alguna mejora interesante. Por ejemplo, aquí estamos diciéndole que, a la hora de mostrar los tipos de dato LocalDateTime en la documentación de la API salgan como String (si no, te sale todos los objetos que contiene LocalDateTime, es un guarreo…)

5. Ya solo falta documentar nuestro api REST. Para eso, documentaremos primero la entity ‘Info’ así:

package com.edwise.pocs.swagger2.entity;

// imports...

@ApiModel(value = "Info entity", description = "Complete data of a entity Info")
public class Info {

    @ApiModelProperty(value = "The id of the info", required = false)
    private Long id;

    @ApiModelProperty(value = "The text of the info", required = true)
    private String infoText;

    @ApiModelProperty(value = "The date of the info", required = true)
    private LocalDateTime creationDateTime;

    // getters and settters
}

Apenas hay cambios respecto a la versión anterior. Usamos las anotaciones @ApiModel y @ApiModelProperty para documentar nuestro pojo.

6. Y ahora documentamos nuestro controller:

package com.edwise.pocs.swagger2.controller;

// imports...

@RestController
@RequestMapping("/api/infos")
@Api(value = "infos", description = "Infos API", produces = "application/json")
public class InfoController {

    @Autowired
    private InfoService infoService;

    @ResponseStatus(HttpStatus.OK)
    @RequestMapping(method = RequestMethod.GET,
            produces = MediaType.APPLICATION_JSON_VALUE)
    @ApiOperation(value = "Get Infos", notes = "Returns all infos")
    @ApiResponses({
            @ApiResponse(code = 200, message = "Exits one info at least")
    })
    public List<Info> getAllInfos() {
        return infoService.findAll();
    }

    @ResponseStatus(HttpStatus.OK)
    @RequestMapping(method = RequestMethod.GET, value = "{id}",
            produces = MediaType.APPLICATION_JSON_VALUE)
    @ApiOperation(value = "Get one Info", notes = "Returns one info")
    @ApiResponses({
            @ApiResponse(code = 200, message = "Exists this info")
    })
    public Info getInfo(@ApiParam(defaultValue = "1", value = "The id of the info to return")
                        @PathVariable Long id) {
        return infoService.findOne(id);
    }

    @RequestMapping(method = RequestMethod.POST,
            produces = MediaType.APPLICATION_JSON_VALUE)
    @ApiOperation(value = "Create Info", notes = "Create a info")
    @ApiResponses({
            @ApiResponse(code = 201, message = "Successful create of a info")
    })
    public ResponseEntity<Info> createInfo(@RequestBody Info info) {
        Info infoCreated = infoService.save(info);
        return new ResponseEntity<>(createHeadersWithLocation(infoCreated),
                HttpStatus.CREATED);
    }

    @ResponseStatus(HttpStatus.NO_CONTENT)
    @RequestMapping(method = RequestMethod.PUT, value = "{id}")
    @ApiOperation(value = "Update Info", notes = "Update a info")
    @ApiResponses({
            @ApiResponse(code = 204, message = "Successful update of a info")
    })
    public void updateInfo(@ApiParam(defaultValue = "1", value = "The id of the info to update")
                           @PathVariable Long id,
                           @RequestBody Info info) {
        infoService.update(info.setId(id));
    }

    @ResponseStatus(HttpStatus.NO_CONTENT)
    @RequestMapping(method = RequestMethod.DELETE, value = "{id}")
    @ApiOperation(value = "Delete Info", notes = "Delete a info")
    @ApiResponses({
            @ApiResponse(code = 204, message = "Successful delete of a info")
    })
    public void deleteInfo(@ApiParam(defaultValue = "1", value = "The id of the info to delete")
                           @PathVariable Long id) {
        infoService.delete(id);
    }

    private HttpHeaders createHeadersWithLocation(Info info) {
        HttpHeaders httpHeaders = new HttpHeaders();
        httpHeaders.setLocation(
                ServletUriComponentsBuilder
                        .fromCurrentRequest()
                        .path("/{id}")
                        .buildAndExpand(info.getId())
                        .toUri());
        return httpHeaders;
    }
}

Tampoco cambia nada, seguimos usando las mismas anotaciones para los controlers: @Api, @ApiOperation, @ApiResponses, @ApiParam
En el anterior post sobre Swagger las explico un poco por encima, aunque creo que viendo el código es bastante sencillo entender como funcionan.

7. Arrancamos nuestro proyecto SpringBoot (mvn spring-boot:run). Si accedemos a http://localhost:8080/swagger-ui.html podemos ver la interfaz de Swagger y juguetear con nuestro API.
ScreenSwagger2

Si queremos acceder directamente a la info (en formato json) de Swagger, accedemos a http://localhost:8080/v2/api-docs?group=api-infos. El formato ha cambiado bastante respecto a la versión anterior, echadle un vistazo.

El código de este post está en mi github, proyecto springfox-swagger2-example.

Spring Boot series: developer tools

Hoy vuelvo con un pequeño post sobre Spring Boot. Hace poco salió la versión 1.3, con varias novedades, entre las que destacan las «developer tools». Vamos a ver un pequeño ejemplo para ver como funciona esta nueva funcionalidad.

Concepto

En la última versión de Spring Boot que ha liberado la gente de Spring hay varias novedades, pero la que más llama la atención son las developer tools, unas librerías para hacer más cómodo el desarrollo. Principalmente lo que nos ofrece es que, mientras estemos desarrollando en nuestro IDE, cualquier cambio que hagamos en el código se ‘auto-despliegue’ en la aplicación, para evitarnos el tener que estar parando y arrancando cada vez que hagamos un cambio.

El truco por lo visto es que Spring Boot, aunque realmente hace un ‘restart’, lo hace muy rápido gracias a que tiene dos class loaders, uno con los jars de nuestro proyecto, y otro con nuestras clases. Cuando se realiza ese ‘restart’ solo recarga el de nuestras clases, lo cual, aunque sea un reinicio completo, es MUY rápido.

Y lo mejor es que funciona como casi todo en Spring Boot: no necesita ningún tipo de configuración ni nada, con añadir la dependencia necesaria a nuestro maven (o gradle), es suficiente.

Voy a implementar un pequeño ejemplo para probarlo, tanto con Intellij como con STS, con maven.

Entorno usado:
Java JDK 1.8
Maven 3.2.5
Git 2.6.3
IDEs Intellij 15.0.1 Ultimate version / STS 3.7.2

Pasos
1. Creamos un proyecto Spring Boot básico, con el starter web nada más, aparte de las dev tools. Nuestro pom.xml quedaría así:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.edwise.pocs.springbootdevtoolstest</groupId>
    <artifactId>springboot-devtools-test</artifactId>
    <version>0.1</version>
    <packaging>jar</packaging>

    <name>springboot-devtools-test</name>
    <description>Test project for Spring Boot new featture: devtools</description>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.3.0.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

No tiene nada nuevo de cualquier ejemplo básico con Spring Boot, aparte de añadir la dependencia ‘spring-boot-devtools‘.

2. Y nuestra clase base de Spring Boot, como siempre:

package com.edwise.pocs.springbootdevtoolstest;

// imports...

@SpringBootApplication
public class Application {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

3. Arrancamos en nuestro Intellij, ya sea con una ‘Run Configuration’ de Spring Boot si tenemos la versión Ultimate, o directamente ejecutando el comando maven ‘spring-boot:run’ desde la tool window de maven.

4. Ahora que tenemos arrancado el servidor, vamos a hacer un cambio: añadimos un controller sencillito, un hello world:

package com.edwise.pocs.springbootdevtoolstest.controllers;

// imports...

@RestController
@RequestMapping("/helloworld")
public class HelloWorldController {

    @RequestMapping(method = RequestMethod.GET, produces = MediaType.TEXT_PLAIN_VALUE)
    public String getHelloWorld() {
        return "Hello World!";
    }
}

5. Y ahora pulsamos el botón de «Make» en Intellij (CTRL + F9). Esto automáticamente recargará todo el contexto de nuestro proyecto. Si accedemos ahora a ‘http://localhost:8080/helloworld‘, nos responderá correctamente. Todo esto sin reiniciar ni parar y arrancar el servidor. Y sin hacer ningún tipo de configuración especial en nuestro IDE ni nada. Y es un redespliegue muy rápido, a mi me tarda menos de 2 segundos 🙂

Nota sobre STS: si hacemos la misma prueba con STS, es algo más sencillo. En ese caso, lo único que necesitamos para «lanzar» la recarga de nuestro proyecto es guardar el archivo, dado que en eclipse está activado por defecto el «Build automatically». Esto hace que en Intellij sea algo menos «automático», pero es debido a la manera de trabajar de este último, que es algo distinta en cuanto a los guardados de los ficheros.

He subido a mi github (proyecto springboot-devtools-test) el ejemplo, con algún controller más, para que lo probéis directamente en vuestros IDEs.

Spring Boot series: cómo cambiar el banner de arranque

Hoy vuelvo con la serie sobre Spring Boot con un sencillo post, en este caso para explicar una pequeña característica que podemos configurar en nuestro proyecto Spring Boot, el banner que se muestra al arrancar.

Concepto:
Si habéis probado Spring Boot, habréis visto que al arrancar, en la consola, sale algo como esto:

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v1.2.5.RELEASE)

...

Bueno, pues este banner que se nos muestra al arrancar, al igual que otras características de Spring Boot, se puede configurar y cambiar muy fácilmente. Vamos a ver un ejemplo de como poner lo que nosotros queramos o incluso desactivarlo.

Entorno usado:
Java JDK 1.8
Maven 3.2.5
Git 1.9.5
IDE Intellij 14.1.4 Ultimate version

Pasos:

1. Creamos un proyecto Spring Boot básico, con maven. En el pom añadimos las dependencias y plugin básicos de Spring Boot, y creamos nuestra clase básica con el método main para arrancar Spring Boot. Para esto podéis seguir los pasos de mi post de PoC con Spring Boot o usar la web de start.spring.io que os genera todo fácilmente.

2. Si probamos a arrancar nuestra aplicación (‘mvn spring-boot:run’), veremos el banner por defecto de Spring que comentaba antes.

3. Vamos a cambiarlo. Es tan sencillo como añadir un nuevo fichero a nuestra carpeta ‘resources’, llamado ‘banner.txt‘. El texto que pongamos en este txt es el que saldrá como banner. En este txt podemos hacer referencia a algunas variables como la versión de Spring Boot (más info aquí). Por ejemplo, podríamos tener un banner así:

  __   __ _   __  ____  _  _  ____  ____    ____   __   _  _       __   __ _   __  ____  _  _  ____  ____    ____  _  _   ___
 / _\ (  ( \ /  \(_  _)/ )( \(  __)(  _ \  (    \ / _\ ( \/ )_    / _\ (  ( \ /  \(_  _)/ )( \(  __)(  _ \  (  _ \/ )( \ / __)
/    \/    /(  O ) )(  ) __ ( ) _)  )   /   ) D (/    \ )  /( )  /    \/    /(  O ) )(  ) __ ( ) _)  )   /   ) _ () \/ (( (_ \
\_/\_/\_)__) \__/ (__) \_)(_/(____)(__\_)  (____/\_/\_/(__/ (/   \_/\_/\_)__) \__/ (__) \_)(_/(____)(__\_)  (____/\____/ \___/

-> Spring Boot version: ${spring-boot.formatted-version} <-

@edwise

Si queréis ponerle letras en plan ‘ascii’, hay webs como esta: http://patorjk.com/software/taag/ 😉

4. Si volvemos a arrancar nuestra aplicación, veremos nuestro nuevo banner:

  __   __ _   __  ____  _  _  ____  ____    ____   __   _  _       __   __ _   __  ____  _  _  ____  ____    ____  _  _   ___
 / _\ (  ( \ /  \(_  _)/ )( \(  __)(  _ \  (    \ / _\ ( \/ )_    / _\ (  ( \ /  \(_  _)/ )( \(  __)(  _ \  (  _ \/ )( \ / __)
/    \/    /(  O ) )(  ) __ ( ) _)  )   /   ) D (/    \ )  /( )  /    \/    /(  O ) )(  ) __ ( ) _)  )   /   ) _ () \/ (( (_ \
\_/\_/\_)__) \__/ (__) \_)(_/(____)(__\_)  (____/\_/\_/(__/ (/   \_/\_/\_)__) \__/ (__) \_)(_/(____)(__\_)  (____/\____/ \___/

-> Spring Boot version:  (v1.2.5.RELEASE) <-

@edwise

...

5. ¿Y si no queremos banner? Aparte de dejar el fichero banner.txt vacio, también podemos hacerlo programaticamente con la clase SpringApplication, en nuestra clase ‘main’:

package com.edwise.springbootseries.banner;

// imports...

@SpringBootApplication
public class Application {

    public static void main(String[] args) {
        SpringApplication app = new SpringApplication(Application.class);
        app.setShowBanner(false);
        app.run(args);
    }
}

Con llamar al método ‘setShowBanner‘ con un ‘false’, desactivaríamos el banner.

Y hasta aquí este mini-post 🙂 Si queréis podéis descargaros el proyecto completo de mi github, proyecto springbootseries-banner.

Implementar un cliente REST con Spring: RestTemplate

He hablado ya mucho de servicios REST y de como montarlos pero, y ¿si queremos acceder a un servicio REST desde nuestra aplicación? Hay varias maneras, desde hacerlo «a pelo» creando una conexión http, o usando clientes Rest. Para este post vamos a ver en concreto el cliente de Spring, RestTemplate.

Concepto:
Spring nos provee de un cliente Rest, RestTemplate, muy sencillo de usar. El se encarga de realizar la conexión http, lo único que hace falta es pasarle la url del servicio contra el que conectar. El mismo RestTemplate gestiona sus propios ‘messageConverters’, con los que parsear los datos enviados y recibidos de/a json, por ejemplo. Solo es necesario añadir en nuestro proyecto la dependencia necesaria (Jackson, en nuestro caso).

En el ejemplo veremos como llamar al servicio con los métodos básicos (GET, POST, PUT, DELETE). Para hacerlo más sencillo, lo haremos directamente desde un programa java desde su método ‘main’. Además, lo haremos atacando contra un servicio definido en apiary. También realizaremos otro ejemplo contra un servicio arrancado en local, en concreto el proyecto integrationtests-rest-example de mi github.

Entorno usado:
Java JDK 1.8
Maven 3.2.1
Git 1.9.5
IDE Intellij 14.1.2 Ultimate version

Ejemplo con apiary:

1. Voy a usar como servicio uno definido en mi cuenta de apiary, con un recurso ‘book’ Para el que no conozca apiary, es una web que nos ofrece el poder definir ‘mocks’ de servicios REST, contra los que poder atacar. Es muy útil a la hora de desarrollar.
En concreto vamos a atacar contra este servicio: http://docs.booksapi.apiary.io/

2. Nos creamos un proyecto maven básico (por ejemplo, con el archetype ‘maven-archetype-quickstart’), y añadimos las siguientes dependencias:

   
    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-web</artifactId>
            <version>4.1.6.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
            <version>2.5.3</version>
        </dependency>
        ...
    </dependencies>

La dependencia de ‘spring-web’ es precisamente para poder tener acceso a la clase RestTemplate. La ‘jackson-databind’ es necesaria para el parseo de datos a json.

3. Creamos una clase con método main, en la que implementaremos los ejemplos:

package com.edwise.pocs.springrestclient;

public class AppBookRestApiary {

    private static final String URL_API_BOOKS =
            "http://private-114e-booksapi.apiary-mock.com/books/";

    public static void main(String[] args) {

    }

}

Tendremos la url a nuestro servicio (http://private-114e-booksapi.apiary-mock.com/books/) directamente en una constante.

4. Creamos una clase para la entity que nos devuelve el servicio:

package com.edwise.pocs.springrestclient.model;

public class Book {
    private Long id;
    private String title;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    @Override
    public String toString() {
        return "Book{" +
                "id=" + id +
                ", title='" + title + '\'' +
                '}';
    }
}

Necesitamos este bean o entity para recuperar los datos, etc. Es tan sencillo como revisar el servicio, ver sus campos e implementar una clase java con esos campos. Jackson se encargará del parseo a/desde json.

5. Creamos nuestro objeto RestTemplate, con el que accederemos al servicio. Al crearse, por defecto ya tiene los converters necesarios para el parseo a/desde json, al tener como dependencia la librería Jackson.

    public static void main(String[] args) {
        RestTemplate restTemplate = new RestTemplate();

    }

Es asi de simple. Tiene también disponible algún otro constructor, con parámetros, como los converters, etc. Pero con el constructor sin parámetros nos sobra.

6. Ahora probaremos cada uno de los métodos que acepta el servicio:

– GET all: para obtener todos los ‘books’:

    ResponseEntity<Book[]> response =
                restTemplate.getForEntity(URL_API_BOOKS, Book[].class);

    System.out.println();
    System.out.println("GET All StatusCode = " + response.getStatusCode());
    System.out.println("GET All Headers = " + response.getHeaders());
    System.out.println("GET Body (object list): ");
    Arrays.asList(response.getBody())
                .forEach(book -> System.out.println("--> " + book));

Tanto para get como para post, tenemos disponible dos tipos de métodos: getForObject/postForObject y getForEntity/postForEntity. Los segundos te devuelven una respuesta completa (es el que uso en el ejemplo), en la que poder obtener tanto headers, como código http devuelto, etc. Los ‘xxxForObject’ devuelven directamente el objeto (el body de la respuesta).
En este caso, le tenemos que pasar la url y el tipo esperado para el body (array de clase ‘Book’ en nuestro caso). Mostramos luego el código http, los headers y, en bucle, todos los ‘books’ devueltos. El método ‘getBody’ nos devolverá un ‘Book[]’, al haberlo parametrizado así en la llamada al get.

– GET: para obtener un ‘book’ en concreto, por su id:

    ResponseEntity<Book> response =
                restTemplate.getForEntity(URL_API_BOOKS + "{id}", Book.class, 12L);

    System.out.println();
    System.out.println("GET StatusCode = " + response.getStatusCode());
    System.out.println("GET Headers = " + response.getHeaders());
    System.out.println("GET Body (object): " + response.getBody());

Similar al caso anterior. En este caso, le pasamos Book.class como tipo esperado.
Por otro lado, el tercer parámetro serían las ‘pathvariables’ del endpoint. En este caso el id.

– POST: para insertar un ‘book’ nuevo:

    Book bookToInsert = createBook(null, "New book title");
    ResponseEntity<Book> response =
                restTemplate.postForEntity(URL_API_BOOKS, bookToInsert, Book.class);

    System.out.println();
    System.out.println("POST executed");
    System.out.println("POST StatusCode = " + response.getStatusCode());
    System.out.println("POST Header location = " + response.getHeaders().getLocation());

Creamos un nuevo ‘Book’, sin id, y lo enviamos por post, con un ‘postForEntity’. En el segundo parámetro le pasamos lo que seria el ‘body’ de la request (nuestro objeto) y en el tercer parámetro el tipo.
En este caso no mostramos el body de la respuesta, dado que lo único que nos devuelve es el ‘location’ (más info en mi anterior post)

– PUT: para actualizar un ‘book’ en concreto, por su id:

    Book bookToUpdate = createBook(123L, "Book title updated");
    restTemplate.put(URL_API_BOOKS + "{id}", bookToUpdate, 123L);

    System.out.println();
    System.out.println("PUT executed");

Aquí cambia un poco la cosa. Excepto para get y post, para el resto de métodos http, RestTemplate nos ofrece unos métodos más simples, demasiado en mi opinión. Como vemos en este caso, el método ‘put’ no devuelve nada. Si necesitáis poder comprobar el código http, los headers o algún otro dato de la respuesta, echadle un vistazo al método ‘exchange’. Es un método con el que podemos realizar cualquier tipo de llamada (GET, POST, etc).

– DELETE: para borrar un ‘book’ en concreto, por su id:

    restTemplate.delete(URL_API_BOOKS + "{id}", 12L);

    System.out.println();
    System.out.println("DELETE executed");

Igual que el caso de put 😦

Ejemplo contra un servicio en local:

1. Si no lo tenemos ya, nos descargamos mi proyecto integrationtests-rest-example de mi github, y lo arrancamos con ‘mvn spring-boot:run’.

2. Como antes, creamos una clase con método main, en la que implementaremos los ejemplos (igual que antes) y también creamos una clase para la entity que nos devuelve el servicio:

package com.edwise.pocs.springrestclient.model;

import java.time.LocalDateTime;

public class Info {

    private Long id;
    private String infoText;
    private LocalDateTime creationDateTime;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getInfoText() {
        return infoText;
    }

    public void setInfoText(String infoText) {
        this.infoText = infoText;
    }

    public LocalDateTime getCreationDateTime() {
        return creationDateTime;
    }

    public void setCreationDateTime(LocalDateTime creationDateTime) {
        this.creationDateTime = creationDateTime;
    }

    @Override
    public String toString() {
        return "Info{" +
                "id=" + id +
                ", infoText='" + infoText + '\'' +
                ", creationDateTime=" + creationDateTime +
                '}';
    }
}

En este caso usamos la nueva API Date de Java 8. Para que Jackson la parsee correctamente, tenemos que añadir una dependencia más:

    <dependencies>
        ...
        <dependency>
            <groupId>com.fasterxml.jackson.datatype</groupId>
            <artifactId>jackson-datatype-jsr310</artifactId>
            <version>2.5.3</version>
        </dependency>
    </dependencies>

4. El resto es prácticamente igual al caso anterior, no voy a poner el código, pero está disponible en mi github: AppInfoRestLocal

Tenéis todo el código de los dos ejemplos preparado para probarlo en mi github, proyecto spring-rest-client-example. Se puede ejecutar cada ejemplo ejecutando su clase ‘main’, la cual ejecuta todos los casos.

Cómo devolver el resultado de un POST en un servicio REST

En varios posts he implementado, con un framework u otro, un servicio REST normalmente muy sencillo. Al ser tan sencillos normalmente no cumplo al 100% las «especificaciones» o buenas prácticas que se recomiendan para considerar un servicio como REST. En este post voy a explicar como se debe implementar un POST, el resultado que devolvemos en él y en concreto el modo de hacerlo en Java.

Concepto:

En la mayoría de ejemplos de servicios REST que he implementado en este blog, para no complicar el código, lo que hago es simplemente devolver un 201 (CREATED). Incluso en algunos casos devuelvo en el body el objeto completo recién creado. Pero lo más correcto es no devolver nada en el ‘body’, y devolver en el ‘location’ de los ‘headers’ la url con la que acceder a ese recurso recién creado. La respuesta debería ser algo así:

HTTP/1.1 201 Created
Server: Apache-Coyote/1.1
Location: http://localhost:8080/api/info/12345
Content-Length: 0

Vamos a ver como devolver esta respuesta tanto en un proyecto con Spring MVC como en un proyecto con JAX-RS. Para ello modificaré los REST que hice para los posts de implementar un servicio Rest con Spring e implementar un servicio Rest con JAX-RS (Jersey).

Por último, comentaremos brevemente como sería si usáramos Spring HATEOAS.

Entorno usado:
Java JDK 1.8
Maven 3.2.1
Git 1.9.5
IDE Intellij 14.1.1 Ultimate version

Con Spring MVC:

1. Usaremos el proyecto spring-rest-example el cual implementé en mi post Implementar un servicio Rest con Spring. Si abrimos nuestro controller, veremos que en el caso del POST no devolvemos nada:

    @RequestMapping(method = RequestMethod.POST)
    @ResponseStatus(HttpStatus.CREATED)
    public void insertUser(@RequestBody User user) {
        userService.save(user);
    }

2. Lo que necesitamos es devolver en los headers el location relleno con la url del objeto creado, sería algo así:

    @RequestMapping(method = RequestMethod.POST)
    public ResponseEntity<User> insertUser(@RequestBody User user) {
        User userSaved = userService.save(user);
        HttpHeaders httpHeaders = new HttpHeaders();
        httpHeaders.setLocation(...);

        return new ResponseEntity<>(httpHeaders, HttpStatus.CREATED);
    }

Creamos un objeto HttpHeaders, y necesitamos setearle el location (de tipo URI). Y en el return del método devolvemos un ResponseEntity con esos headers y el código HTTP de respuesta que corresponde a un POST (201 – Created).

La primera idea que se nos puede venir a la cabeza es meterle la url directamente, con un String o quizá obteniendo el dominio de alguna variable o propiedad, que tengamos en una constante. Con esto funcionaria, pero… vamos a hacerlo un poco mejor 🙂

3. Usaremos un builder que nos provee Spring para crear URIs: ServletUriComponentsBuilder, de la siguiente manera:

    @RequestMapping(method = RequestMethod.POST)
    public ResponseEntity<User> insertUser(@RequestBody User user) {
        User userSaved = userService.save(user);
        HttpHeaders httpHeaders = new HttpHeaders();
        httpHeaders.setLocation(
                ServletUriComponentsBuilder
                        .fromCurrentRequest()
                        .path("/{id}")
                        .buildAndExpand(userSaved.getId())
                        .toUri());

        return new ResponseEntity<>(httpHeaders, HttpStatus.CREATED);
    }

Con esto creamos nuestra URI, los métodos del builder son bastante autoexplicativos: con la request actual, le añadimos un path variable ‘/{id}’, y le metemos el id en esa variable.
(El código en mi github está algo refactorizado, pero es lo mismo 😛 )

4. Arrancando con el plugin maven de Spring Boot, como siempre, podemos probar nuestro POST. La respuesta del POST /api/users será algo así:

HTTP/1.1 201 Created
Server: Apache-Coyote/1.1
Location: http://localhost:8080/api/users/45332
Content-Length: 0
Date: Mon, 06 Apr 2015 18:23:50 GMT

5. (Bonus) Testing: Si tenemos test unitario de nuestro controller, se complica un poco. Tenemos que, o bien mockear los métodos del builder (es estático, necesitaríamos PowerMock o similar), o simplemente añadimos una MockHttpServletRequest al contexto, cosa que es lo que he hecho en el método @Before, para no complicarme mucho. El test completo, aquí
Si lo que tenemos son tests de integración, no habría que cambiar nada si el test está bien configurado.

Con JAX-RS (Jersey):

1. Usaremos el proyecto jersey-rest-example el cual implementé en mi post Implementar un servicio REST con JAX-RS (Jersey). Si abrimos nuestro controller, ahora mismo tenemos lo siguiente:

    @POST
    @Consumes(MediaType.APPLICATION_JSON)
    public Response insertUser(User user) {
        userService.save(user);
        return Response.status(Response.Status.CREATED).build();
    }

2. Necesitamos, igual que antes devolver en los headers el location relleno con la url de nuestro recurso, en este caso, de esta manera:

    @POST
    @Consumes(MediaType.APPLICATION_JSON)
    public Response insertUser(User user) {
        User userSaved = userService.save(user);
        URI uri = ...;
        return Response.created(uri).build();
    }

Aquí devolvemos un Response, que creamos con su método ‘created’ (esto le asigna ya como http status el 201 – Created) y le pasamos la uri que se asignará en el location.

3. Y al igual que antes, vamos a hacerlo con un builder que nos de la url, en este caso usaremos uno de la especificación JAX-RS: UriBuilder. Para ello, eso sí, necesitamos inyectar en nuestro controller un UriInfo, que se obtiene del contexto de la aplicación:

    @POST
    @Consumes(MediaType.APPLICATION_JSON)
    public Response insertUser(User user) {
        User userSaved = userService.save(user);
        UriBuilder uriBuilder = uriInfo.getRequestUriBuilder();
        URI uri = uriBuilder.path(userSaved.getId().toString()).build();
        return Response.created(uri).build();
    }

Necesitamos un bean de contexto, UriInfo, con el que podremos obtener el builder concreto para nuestra URI. una vez con el builder, con su método ‘path’ le añadimos el id de nuestro recurso, y listo.
(El código en mi github está algo refactorizado, pero es lo mismo 😛 )

4. Arrancamos la aplicación (ya sea con un tomcat o con un glassfish, como vimos en el post de JAX-RS), y el servicio nos devolverá lo siguiente al hacer el POST:

HTTP/1.1 201 Created
Server: GlassFish Server Open Source Edition  4.1
X-Powered-By: Servlet/3.1 JSP/2.3 (GlassFish Server Open Source Edition  4.1  Java/Oracle Corporation/1.8)
Location: http://localhost:8080/api/users/45332
Date: Mon, 06 Apr 2015 18:50:13 GMT
Content-Length: 0

5. (Bonus) Testing: aquí, si también tenemos test unitario, igualmente se complica un poco. En este caso si he mockeado todo el tema de la UriInfo. El tests completo, aquí.

Con Spring HATEOAS:

No quiero entrar mucho en detalle, porque creo que esto da para otro post aparte que escribiré pronto, pero no quería terminar este post sin nombrar el concepto HATEOAS, así como el modulo de Spring que lo implementa. Si usamos Spring HATEOAS en nuestro proyecto, no necesitamos obtener el link a través de ningún builder, ya que nuestro propio recurso tendrá su link «self». Más información sobre HATEOAS aquí, así como sobre el proyecto Spring. Lo veremos en otro post.

Nada más. Recordad que todo el código de este post está subido en los proyectos spring-rest-example y jersey-rest-example de mi github.

Tests de integración para un servicio REST, con Spring

En mis dos últimos post he explicado como configurar los tests de integración con Spring Boot y como configurarlos además con Maven. Teniendo ya toda esa configuración, es hora ya de implementar unos tests de integración completos, para un servicio REST.

Concepto:
Ya expliqué en los anteriores post el concepto de tests de integración: en resumen, son tests en los que probamos varios componentes de un sistema trabajando juntos (a diferencia de los unitarios, en los que solo probamos cada componente por separado).
En este ejemplo voy a realizar unos tests de integración completos de un servicio REST (el mismo usado en los últimos posts, un servicio REST para un recurso sencillo ‘Info’). Para realizar los tests correctamente usaré, por un lado el clásico MockMvc de Spring, y para probar los jsons que devuelve el rest usaré la librería jsonpath.

Entorno usado:
Java JDK 1.8
Maven 3.2.1
Git 1.9.5
IDE Intellij 14.0.3 Ultimate version

Pasos:

1. Los tests van a tratar simplemente de probar todo el servicio REST Info el cual tenemos implementado en InfoController:

package com.edwise.pocs.integrationtestsrest.controller;

// imports...

@RestController
@RequestMapping("/api/info/")
public class InfoController {

    @Autowired
    private InfoService infoService;

    @ResponseStatus(HttpStatus.OK)
    @RequestMapping(method = RequestMethod.GET,
            produces = MediaType.APPLICATION_JSON_VALUE)
    public List<Info> getAllInfos() {
        return infoService.findAll();
    }

    @ResponseStatus(HttpStatus.OK)
    @RequestMapping(method = RequestMethod.GET, value = "{id}",
            produces = MediaType.APPLICATION_JSON_VALUE)
    public Info getInfo(@PathVariable Long id) {
        return infoService.findOne(id);
    }

    @RequestMapping(method = RequestMethod.POST,
            produces = MediaType.APPLICATION_JSON_VALUE)
    public ResponseEntity<Info> createInfo(@RequestBody Info info) {
        Info infoCreated = infoService.save(info);
        return new ResponseEntity<>(infoCreated, HttpStatus.CREATED);
    }

    @ResponseStatus(HttpStatus.NO_CONTENT)
    @RequestMapping(method = RequestMethod.PUT, value = "{id}")
    public void updateInfo(@PathVariable Long id, @RequestBody Info info) {
        infoService.update(info.setId(id));
    }

    @ResponseStatus(HttpStatus.NO_CONTENT)
    @RequestMapping(method = RequestMethod.DELETE, value = "{id}")
    public void deleteInfo(@PathVariable Long id) {
        infoService.delete(id);
    }

}

Como se puede ver, he completado el controller de los posts anteriores con un método PUT (para actualizar) y un método GET all, para tener lo que podría ser un servicio REST completo.

2. Añadimos a las dependencias de maven la librería jsonpath, para testear fácilmente jsons:

     ...
     <dependencies>
          ...
         <dependency>
             <groupId>com.jayway.jsonpath</groupId>
             <artifactId>json-path-assert</artifactId>
             <version>1.2.0</version>
             <scope>test</scope>
         </dependency>
     </dependencies>
     ...

2. Comenzamos nuestros tests de integración con la configuración que hicimos en los posts anteriores, tanto de maven como de Spring y Spring Boot. Para ello usaremos la clase que ya creamos, InfoControllerIT:

package com.edwise.pocs.integrationtestsrest.controller;

// imports...

@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = {Application.class})
@WebIntegrationTest({"server.port=0"})
public class InfoControllerIT {

    @Autowired
    protected WebApplicationContext webApplicationContext;

    private MockMvc mockMvc;

    @Before
    public void setUp() {
        mockMvc = MockMvcBuilders
                .webAppContextSetup(this.webApplicationContext)
                .build();
    }

    // TODO aquí implementaremos los tests
}

3. Creamos nuestro primer test. En el vamos a probar el método GET /api/info/{id}, es decir, el que obtiene un objeto Info por su id. Lo implementaremos así:

    @Test
    public void getInfo_InfoFound_ShouldReturnCorrectInfo() throws Exception {
        mockMvc.perform(get("/api/info/{id}", INFO_ID_1234))
                .andExpect(status().isOk())
                .andExpect(content().contentType(MediaType.APPLICATION_JSON))
                .andExpect(jsonPath("$.id", is(INFO_ID_1234.intValue())))
                .andExpect(jsonPath("$.infoText", is(INFO_TEXT_1234)))
                .andExpect(jsonPath("$.creationDateTime", is(notNullValue())));
    }

Todos nuestros tests de integración consisten en una llamada con el objeto MockMvc. En ella se ve como realizamos un get, con un parámetro de path variable (id), y sobre esa llamada hacemos varías comprobaciones (métodos ‘andExpect‘):
– Que el http status devuelto es OK (200).
– Que el contenido recibido es de tipo JSON.
– Usando la librería jsonpath comprobamos que el body de la respuesta contiene un json correcto (en este caso compruebo que el id y el texto son los correctos y que la fecha no es nula, por ejemplo).

4. Implementamos el test que pruebe el método que devuelve todos los Info’s, GET /api/info/:

    @Test
    public void getAll_InfosFound_ShouldReturnFoundInfos() throws Exception {
        mockMvc.perform(get("/api/info/"))
                .andExpect(status().isOk())
                .andExpect(content().contentType(MediaType.APPLICATION_JSON))
                .andExpect(jsonPath("$", hasSize(3)))
                .andExpect(jsonPath("$[0].id", is(INFO_ID_120.intValue())))
                .andExpect(jsonPath("$[0].infoText", is(INFO_TEXT_1234)))
                .andExpect(jsonPath("$[0].creationDateTime", is(notNullValue())))
                .andExpect(jsonPath("$[1].id", is(INFO_ID_121.intValue())))
                .andExpect(jsonPath("$[1].infoText", is(INFO_TEXT_4567)))
                .andExpect(jsonPath("$[1].creationDateTime", is(notNullValue())))
                .andExpect(jsonPath("$[2].id", is(INFO_ID_122.intValue())))
                .andExpect(jsonPath("$[2].infoText", is(INFO_TEXT_7892)))
                .andExpect(jsonPath("$[2].creationDateTime", is(notNullValue())));
    }

El tests es muy similar al anterior, lo único que en este caso nos devuelve un array de Info’s, con lo que comprobamos el tamaño del array (método hasSize), y cada uno de los Info’s por separado, con jsonpath, accediendo a cada Info como si fuera un array.

5. Seguimos con el test que pruebe el método crea un nuevo Info, POST /api/info/, y el que actualice un Info ya existente, PUT /api/info/{id}:

    @Test
    public void postInfo_InfoCorrect_ShouldReturnCreatedStatusAndCorrectInfo() throws Exception {
        mockMvc.perform(post("/api/info/")
                .contentType(MediaType.APPLICATION_JSON)
                .content("{\"infoText\":\"Info 1234 New\",\"creationDateTime\":\"2013-10-11T20:10:10\"}"))
                .andExpect(status().isCreated())
                .andExpect(content().contentType(MediaType.APPLICATION_JSON))
                .andExpect(jsonPath("$.id", is(INFO_ID_1234.intValue())))
                .andExpect(jsonPath("$.infoText", is(INFO_TEXT_1234_NEW)))
                .andExpect(jsonPath("$.creationDateTime", is(notNullValue())));
    }

    @Test
    public void putInfo_InfoCorrect_ShouldReturnNoContentStatus() throws Exception {
        mockMvc.perform(put("/api/info/{id}", INFO_ID_1234)
                .contentType(MediaType.APPLICATION_JSON)
                .content("{\"infoText\":\"Info 1234 Updated\",\"creationDateTime\":\"2014-10-25T19:16:21\"}"))
                .andExpect(status().isNoContent());
    }

En el primero, realizamos un POST y le marcamos como ‘Content-Type’ del mismo el típo ‘application/json’. Luego le pasamos como contenido de la request el json del objeto a crear. Las comprobaciones son las de siempre, comprobando en este caso que el http status es CREATED (201).
En el segundo, realizamos un PUT, con path variable (el id de siempre) y hacemos lo mismo que en el POST, excepto que no comprobamos el json del body (no devuelve nada) y que el http status esperado es NO CONTENT (204).

6. Por último, implementamos el que pruebe el método que borre un Info, DELETE /api/info/{id}:

    @Test
    public void deleteInfo_ShouldReturnNoContentStatus() throws Exception {
        mockMvc.perform(delete("/api/info/{id}", INFO_ID_1234))
                .andExpect(status().isNoContent());
    }

Realizamos un DELETE, con el id path variable, y solo es necesario comprobar el http status, NO CONTENT (204).

7. Y hemos terminado. Nuestra clase completa ‘InfoControllerIT’ quedaría así. Podemos ahora ejecutar los tests tanto desde Intellij como con maven (profile ‘integration-test’, con el goal ‘verify’).

8. Temas importantes a tener en cuenta:
– El servicio REST implementado es muy sencillo y tiene la capa de datos mockeada. Si tuviéramos un servicio completo, con su capa de base de datos, habría que buscar como configurar una base de datos en memoria o similar, para realizar los tests (o incluso mockear con mockito la capa de datos).
– Faltarían varios tests, principalmente tests que fallen: GET, PUT y DELETE sobre un Info que no exista, etc.
– El servicio REST, al realizar un POST, devuelve en el body de la response el json completo. Realmente, lo más correcto sería no devolver nada en el body, y devolver en el ‘location’ de los headers un link al nuevo Info creado (principio HATEOAS). Esto probablemente lo veamos en otro post más adelante.

Si queréis descargaros el proyecto completo, lo tenéis como siempre en mi github (proyecto integrationtests-rest-example).

Spring Boot series: cómo configurar tus tests de integración

Vuelvo con Spring Boot, esta vez con un pequeño post en el que voy a explicar como configurar fácilmente los tests de integración con él. Y así empiezo a tocar por fin un poco el testing, que ya me apetecía escribir un post sobre ello 🙂

Concepto:
Mientras que los tests unitarios son tests en los que probamos cada unidad / clase / pequeña funcionalidad por separado, en los tests de integración se prueba todo (o casi todo) el sistema (o grupo de componentes) interactuando entre ellos.

También, a diferencia de los tests unitarios, que son muy sencillos de configurar (normalmente no necesitan configuración), los tests de integración suelen ser algo liosos y complejos de configurar. Con Spring, y en concreto con Spring Boot, veremos que es bastante sencillo.

Por otro lado, los tests de integración no están pensados para ejecutarse a menudo (a diferencia de los unitarios), y lo normal es ejecutarlo solo en algunos casos en nuestra maquina local y sobre todo en nuestro sistema de integración continua. Por eso lo normal es tener estos tests en otra carpeta distinta a la típica de los tests unitarios y además tener configurada la ejecución de esos tests en otro ‘goal’ (si usamos maven).
Esto último lo dejaré para explicarlo en otro post. En este me voy a centrar sólo en la parte de configuración de Spring. En este post la clase de test de integración la voy a crear en el mismo directorio ‘test’ de siempre.

El proyecto sobre el que voy a mostrar como configurar los tests de integración es un sencillo servicio REST sobre un recurso ‘Info’. Está implementado con las típicas capas Service y Repository (esta última mockeada).
También dejo para otro post el realizar unos tests de integración completos de un servicio REST.

Entorno usado:
Java JDK 1.8
Maven 3.2.1
Git 1.9.5
IDE Intellij 14.0.3 Ultimate version

Pasos:

1. Creamos el proyecto base sobre el que implementaremos nuestros tests:

  • Creamos un proyecto maven en Intellij, como siempre: File -> New Project, elegimos Maven, checkeamos «Create from archetype» y seleccionamos el quickstart básico o el mio si lo tenéis instalado.
  • Añadimos en nuestro pom.xml las dependencias básicas de Spring Boot: el parent, el starter de web, y el plugin para maven (más info en el post sobre como montar un PoC de Spring Boot, pasos 5 y 6.
  • Creamos como siempre nuestra clase base para Spring Boot, Application.java.
  • Implementamos nuestro REST. Con las siguientes clases: la entity Info, interfaz InfoRepository, implementación ‘mock’ de ese ‘Repository’, interfaz InfoService, e implementación de ese ‘Service’.
  • Por último, implementamos un pequeño controller para terminar nuestro REST. Es bastante básico, solo he implementado los métodos GET, POST y DELETE:
    package com.edwise.springbootseries.integrationtests.controller;
    
    // imports
    
    @RestController
    @RequestMapping("/api/info/")
    public class InfoController {
    
        @Autowired
        private InfoService infoService;
    
        @ResponseStatus(HttpStatus.OK)
        @RequestMapping(method = RequestMethod.GET, value = "{id}",
                produces = MediaType.APPLICATION_JSON_VALUE)
        public Info getInfo(@PathVariable Long id) {
            return infoService.findOne(id);
        }
    
        @RequestMapping(method = RequestMethod.POST,
                produces = MediaType.APPLICATION_JSON_VALUE)
        public ResponseEntity createInfo(@RequestBody Info info) {
            Info infoCreated = infoService.save(info);
            return new ResponseEntity<>(infoCreated, HttpStatus.CREATED);
        }
    
        @ResponseStatus(HttpStatus.NO_CONTENT)
        @RequestMapping(method = RequestMethod.DELETE, value = "{id}")
        public void deleteInfo(@PathVariable Long id) {
            infoService.delete(id);
        }
    }
    

    Como puede verse, usa el ‘InfoService’ definido anteriormente.

2. Vamos ya al lío con nuestro test. Primero, añadimos las dependencias necesarias para testing: el ‘starter’ de tests para spring boot (spring-boot-starter-test). Entre otras librerías, este starter nos incluye junit, mockito, hamcrest, spring-test… Nos quedaría un pom.xml tal que así:

...
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.2.1.RELEASE</version>
    </parent>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>com.fasterxml.jackson.datatype</groupId>
            <artifactId>jackson-datatype-jsr310</artifactId>
            <version>2.5.0</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
...

Como veis, también he añadido también la dependencia de la librería de jackson para parsear la nueva Java date API (jsr310), jackson-datatype-jsr310. La necesitamos ya que nuestro entity tiene un campo fecha de ese tipo. (Revisad mi post anterior sobre Spring Boot y jackson para más información. Importante: para que el formato del campo fecha se parsee correctamente es necesario añadir una propiedad al application.properties, tanto en el directorio ‘main’ como en el ‘test’).

3. Crearemos una clase con sufijo ‘IT’ (es lo que se suele poner a los tests de integración para diferenciarlos de los unitarios) en el subdirectorio ‘test’ y le pondremos varias anotaciones Spring:

package com.edwise.springbootseries.integrationtests;

import org.junit.runner.RunWith;
import org.springframework.boot.test.IntegrationTest;
import org.springframework.boot.test.SpringApplicationConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;

@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = {Application.class})
@WebIntegrationTest({"server.port=0"})
public class InfoControllerIT {
    // tests here
}

Con estas tres anotaciones tendremos un test de integración completamente funcional:
@RunWith(SpringJUnit4ClassRunner.class): runner básico de Spring que necesitamos para cualquier test en el que necesitemos un contexto de Spring.
@SpringApplicationConfiguration: anotación alternativa a la clásica @ContextConfiguration, pero para Spring Boot. Con ella le pasamos información de como configurar el contexto. Lo normal es ponerle la clase básica de Spring Boot (nuestra Application.java). También podríamos añadirle otras clases de configuración especificas para los tests.
@WebIntegrationTest: con esta, le decimos que necesitamos probar una ‘web application’ (nos permitirá crear un mockMvc, entre otras cosas), y además nos levanta un servidor para test completo. Le podemos pasar el puerto en el que arrancar, si le ponemos 0, como es nuestro caso, será aleatorio. Está anotación es similar a poner estas dos: @WebAppConfiguration y @IntegrationTest

4. Para probar una aplicación web o, en este caso, un servicio REST, necesitamos poder lanzar peticiones http. Para simular eso, en Spring, lo normal es usar la clásica clase MockMvc. Spring Boot además provee una nueva clase para testing: TestRestTemplate. Y hay otras librerías, como REST-Assured En este caso, crearemos el test con la MockMvc, en próximos posts entraré más en detalle con el resto.
Primero es necesario construir un MockMvc, para ello añadiremos lo siguiente a nuestra ‘InfoControllerIT’:

...
    @Autowired
    protected WebApplicationContext webApplicationContext;

    private MockMvc mockMvc;

    @Before
    public void setUp() {
        mockMvc = MockMvcBuilders
                .webAppContextSetup(this.webApplicationContext)
                .build();
    }
...

Creamos en el método @Before el mockMvc con el builder que nos provee Spring. Es necesario pasarle como parámetro el objeto con la información de contexto, WebApplicationContext, el cual lo podemos obtener gracias a la anotación @WebIntegrationTest (o @WebAppConfiguration).

5. Con eso ya tendríamos configurado completamente nuestro test de integración. Añadimos ahora 3 tests básicos, para cada uno de los métodos del servicio REST (GET, POST y DELETE):

...

    @Test
    public void getInfo_ShouldReturnCorrectInfo() throws Exception {
        String jsonExpected = "{\"id\":1234,\"info\":\"Info 1234\",\"creationDateTime\":\"2001-12-12T13:40:30\"}";

        mockMvc.perform(get("/api/info/{id}", INFO_ID_1234))
                .andExpect(status().isOk())
                .andExpect(content().contentType(MediaType.APPLICATION_JSON))
                .andExpect(content().string(jsonExpected));
    }

    @Test
    public void postInfo_ShouldReturnCreatedStatusAndCorrectInfo() throws Exception {
        String jsonExpected = "{\"id\":1234,\"info\":\"Info 1234 New\",\"creationDateTime\":\"2015-10-25T19:13:21\"}";

        mockMvc.perform(post("/api/info/")
                .contentType(MediaType.APPLICATION_JSON)
                .content("{\"info\":\"Info 1234 New\",\"creationDateTime\":\"2015-10-25T19:13:21\"}"))
                .andExpect(status().isCreated())
                .andExpect(content().string(jsonExpected));
        ;
    }

    @Test
    public void deleteInfo_ShouldReturnNoContentStatus() throws Exception {
        mockMvc.perform(delete("/api/info/{id}", INFO_ID_1234))
                .andExpect(status().isNoContent());
    }
...

En próximos posts entraré más en detalle, pero el código es bastante autoexplicativo: como se puede ver, en cada test, llamamos al método ‘perform’ de nuestro mockMvc, pasándole si es get, post…, y sobre ese mismo objeto, hacemos varios ‘asserts’ (andExpected es el método concreto) sobre el status devuelvo por la respuesta, contenido, etc.

Nuestra clase de test quedaría finalmente así.

Así de fácil. Si queréis jugar con el código, podéis bajaroslo como siempre de mi github, proyecto springbootseries-integrationtests.

En próximos posts explicaré como separar los tests de integración de los tests unitarios, con maven, e implementaré algún ejemplo más en detalle de como probar un servicio REST a fondo.

Spring Boot series: autoconfiguración de Jackson

Para comenzar este año 2015, vuelvo con los minipost sobre Spring Boot. Esta vez explicaré, con un ejemplo, como configurar el parseo de objetos a json y viceversa con Jackson, muy fácilmente en Spring Boot, sobre todo desde la versión 1.2 de este.

Concepto:
Jackson es una librería Java muy potente para el procesamiento / parseo de objetos a json y viceversa. Ofrece muchas opciones y posibilidades, anotaciones propias, etc. Suele ser casi la librería estándar para esto en los proyectos en los que he trabajado.

En un típico proyecto Spring, como los que he explicado en otros posts, como el de implementar un servicio REST con Spring, suele venir por defecto Jackson.
También en el post de implementar un servicio REST con JAX-RS usé Jackson para lo propio.

Para el caso de Spring, suele ser suficiente con cargar en el contexto un Bean de Jackson (suele ser un ObjectMapper), configurado tal cual lo necesitemos, y ya automáticamente nuestros REST usarán ese «mapper».

Para este ejemplo voy a explicar como configurar Jackson en Spring Boot 1.2 (es más facil aún en esta nueva versión), para el uso de fechas con la librería Joda-time, así como también para fechas con la nueva API de Java 8 (la famosa especificación JSR-310).

Entorno usado:
Java JDK 1.8
Maven 3.2.1
Git 1.9.5
IDE Intellij 14.0.2 Ultimate version
Lombok

Pasos:

Vamos a implementar una pequeña aplicación Spring Boot, con un entity Info y un controller que será un mini servicio REST: vamos a implementar solo el GET de un elemento y el POST, sin servicio ni repositorios. Esto no lo haríamos así si quisiéramos tener una implementación por capas correcta, claro :P. Pero para nuestro ejemplo, es suficiente.
Esta entity Info tendrá un atributo de tipo fecha y hora (LocalDateTime de joda-time primero, y LocalDateTime de la API Java 8 Date después). Y para simplificar el código, usaremos Lombok
El proyecto completo os lo podéis descargar como siempre de mi github, proyecto springboot-series-jackson. Está montado con la librería joda-time, pero también está comentado para usar la librería JSR-310.

¡Al lio!

Con Joda-time

1. Añadimos a nuestro pom.xml de maven lo necesario para montar una aplicación Spring Boot básica:

    ...
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.2.0.RELEASE</version>
    </parent>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        ...
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>               
    ...

Lo de siempre: la dependencia «padre» del proyecto Spring Boot, la dependencia para el starter web y el plugin para maven (para poder arrancarlo). Nada nuevo.

2. Añadimos la dependencia de la librería joda, así como de la sublibrería jackson para joda:

    ...
    <dependencies>
        ...
        <dependency>
            <groupId>joda-time</groupId>
            <artifactId>joda-time</artifactId>
            <version>2.6</version>
        </dependency>

        <dependency>
            <groupId>com.fasterxml.jackson.datatype</groupId>
            <artifactId>jackson-datatype-joda</artifactId>
            <version>2.4.4</version>
        </dependency>
        ...
    </dependencies>
    ...

La librería core de jackson ya nos la incluye Spring, solo necesitamos añadirle la libreria «third party» para el tipo joda-time.
En nuestro pom.xml añadimos también lombok, junit y alguna otra librería. El pom.xml completo lo tenéis aquí.

3. Creamos nuestra clase Application para el arranque de Spring Boot:

package com.edwise.springbootseries.jackson;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class Application {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

La anotación @SpringBootApplication es nueva en la versión 1.2. Simplemente es un ‘atajo’ para las tres anotaciones @ComponentScan, @EnableAutoConfiguration y @Configuration. Nada más.

4. Creamos nuestra clase entity, Info:

package com.edwise.springbootseries.jackson.entity;

import lombok.Data;
import lombok.experimental.Accessors;
import org.joda.time.LocalDateTime;

@Data
@Accessors(chain = true)
public class Info {

    private long id;
    private String info;
    private LocalDateTime creationDateTime;
}

Consta solo de tres atributos, un long, un String y un LocalDateTime. Usamos lombok para que genere el código típico (getters, setter, toString…).

5. Creamos nuestro RestController, solo con un GET y un POST, mockeados (ni servicio, ni repositorio, etc):

package com.edwise.springbootseries.jackson.controller;

import com.edwise.springbootseries.jackson.entity.Info;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.joda.time.LocalDateTime;

@RestController
@RequestMapping("/api/info/")
public class InfoController {

    @ResponseStatus(HttpStatus.OK)
    @RequestMapping(method = RequestMethod.GET, value = "{id}",
            produces = MediaType.APPLICATION_JSON_VALUE)
    public Info getInfo(@PathVariable long id) {
        return new Info()
                .setId(id)
                .setInfo("Info 1234")
                .setCreationDateTime(new LocalDateTime(2001, 12, 12, 13, 40, 30));
    }

    @RequestMapping(method = RequestMethod.POST,
            produces = MediaType.APPLICATION_JSON_VALUE)
    public ResponseEntity createInfo(@RequestBody Info info) {
        return new ResponseEntity<>(info.setId(1234), HttpStatus.CREATED);
    }
}

Nada nuevo tampoco: un método para obtener con GET un Info, por id, y un método para crear un Info completo, por POST.

6. Si probamos a arrancar nuestra aplicación Spring Boot, y accedemos (mediante navegador o cliente REST mejor) a http://localhost:8080/api/info/1, nos devolverá algo como esto:

{
  "id": 1,
  "info": "Info 1234",
  "creationDateTime": [
    2001,
    12,
    12,
    13,
    40,
    30,
    0
  ]
}

Si, un json. Spring Boot nos carga automáticamente en el contexto un bean de jackson para realizar ese parseo. Además, con la nueva versión 1.2, solo por tener en el proyecto la librería jackson de joda (jackson-datatype-joda), también nos carga el JodaModule que usábamos en los posts de montar un servicio REST el solito. No tenemos que hacer nada más.

7. El formato en el que se muestra el atributo fecha por defecto es una especie de array con los valores. Si queremos que sea algo como esto: «2001-12-12T13:40:30.000», en principio tendríamos que añadir al mapper la configuración siguiente, como ya hicimos en alguno de los posts anteriores:

   ...
   objectMapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
   ...

No es necesario hacerlo así. Esto también ha sido simplificado en Spring Boot, y lo único que tenemos que hacer es añadir, en un archivo .properties (o .yml) la configuración que queramos. En este caso, crearíamos un application.properties bajo una carpeta ‘resources’:

spring.jackson.serialization.write_dates_as_timestamps=false

Si probamos a arrancar la aplicación, y accedemos otra vez http://localhost:8080/api/info/1, ahora nos devolverá algo así:

{
  "id": 1,
  "info": "Info 1234",
  "creationDateTime": "2001-12-12T13:40:30.000"
}

Resumiendo: hemos configurado Jackson, con joda-time, y con un formato concreto de fecha, simplemente añadiendo las librerías al proyecto y añadiendo una propiedad a un fichero properties. El resto, se ha encargado Spring Boot 🙂

Con la API Date Java 8 (JSR-310)

Los pasos son casí los mismos que en el anterior, solo voy a detallar los que cambian:

2. Añadimos al pom la dependencia de la librería jackson la nueva api Date en Java 8:

  ...
    <dependencies>
        ...
        <dependency>
            <groupId>com.fasterxml.jackson.datatype</groupId>
            <artifactId>jackson-datatype-jsr310</artifactId>
            <version>2.4.4</version>
        </dependency>
        ...
    </dependencies>
  ...

4. Creamos nuestra entity Info, pero ahora con el LocalDateTime de Java 8… no cambia nada más que el import:

...
import java.time.LocalDateTime;
...

5. Creamos nuestro RestController: solo cambia el import del LocalDateTime, y la forma en que lo creamos en el setter:

...
import java.time.LocalDateTime;

@RestController
@RequestMapping("/api/info/")
public class InfoController {

    @ResponseStatus(HttpStatus.OK)
    @RequestMapping(method = RequestMethod.GET, value = "{id}",
            produces = MediaType.APPLICATION_JSON_VALUE)
    public Info getInfo(@PathVariable long id) {
        return new Info()
                .setId(id)
                .setInfo("Info 1234")
                .setCreationDateTime(LocalDateTime.of(2001, 12, 12, 13, 40, 30));
    }

    ...
}

¡Y listo! el resto de pasos son exactamente iguales: añadiríamos la misma propiedad a nuestro application.properties, y ya tendríamos Jackson funcionando en nuestra aplicación Spring Boot, esta vez usando la nueva API Date de Java 8. Y, de la misma manera, lo único que hemos necesitado para configurarla es añadir la librería jackson de la JSR-310 al proyecto, añadir esa propiedad a nuestro properties.

Como comente antes, el proyecto completo está en mi github, proyecto springboot-series-jackson 🙂