# Crear el proyecto Spring Boot


## Configurar el proyecto

Crea un proyecto Spring Boot desde [Spring Initializr](https://start.spring.io)
seleccionando los starters **Spring Web**, **Spring Reactive Web** y **Testcontainers**.

Alternativamente, puedes clonar el
[repositorio de la guía](https://github.com/testcontainers/tc-guide-testing-rest-api-integrations-using-mockserver).

Después de generar el proyecto, agrega las bibliotecas **REST Assured** y **MockServer**
como dependencias de prueba. Las dependencias clave en `pom.xml` son:

```xml
<properties>
    <java.version>17</java.version>
    <testcontainers.version>2.0.4</testcontainers.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-starter-webflux</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>org.testcontainers</groupId>
        <artifactId>testcontainers-junit-jupiter</artifactId>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>org.testcontainers</groupId>
        <artifactId>testcontainers-mockserver</artifactId>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>org.mock-server</groupId>
        <artifactId>mockserver-netty</artifactId>
        <version>5.15.0</version>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>io.rest-assured</groupId>
        <artifactId>rest-assured</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>
```

Se recomienda utilizar el BOM (Bill of Materials) de Testcontainers para que no
tengas que repetir la versión en cada dependencia de módulo de Testcontainers.

Esta guía construye una aplicación que gestiona álbumes de video. Una API REST de
terceros maneja los recursos de fotos. Con fines de demostración, la aplicación
utiliza la API pública [JSONPlaceholder](https://jsonplaceholder.typicode.com/)
como servicio de fotos.

La aplicación expone un endpoint `GET /api/albums/{albumId}` que llama al servicio
de fotos para obtener las fotos de un álbum específico.
[MockServer](https://www.mock-server.com/) es una biblioteca para simular servicios
basados en HTTP. Testcontainers proporciona un
[módulo de MockServer](https://java.testcontainers.org/modules/mockserver/) que
ejecuta MockServer como un contenedor Docker.

## Crear los modelos Album y Photo

Crea `Album.java` utilizando Java records:

```java
package com.testcontainers.demo;

import java.util.List;

public record Album(Long albumId, List<Photo> photos) {}

record Photo(Long id, String title, String url, String thumbnailUrl) {}
```

## Crear la interfaz PhotoServiceClient

Spring Framework 6 introdujo
[soporte para clientes HTTP declarativos](https://docs.spring.io/spring-framework/reference/integration/rest-clients.html#rest-http-interface).
Crea una interfaz con un método que obtenga las fotos para un ID de álbum determinado:

```java
package com.testcontainers.demo;

import java.util.List;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.service.annotation.GetExchange;

interface PhotoServiceClient {
  @GetExchange("/albums/{albumId}/photos")
  List<Photo> getPhotos(@PathVariable Long albumId);
}
```

## Registrar PhotoServiceClient como un bean

Para generar una implementación en tiempo de ejecución de `PhotoServiceClient`,
regístralo como un bean de Spring utilizando `HttpServiceProxyFactory`. La fábrica
requiere una implementación de `HttpClientAdapter`. Spring Boot proporciona
`WebClientAdapter` como parte de la biblioteca `spring-webflux`:

```java
package com.testcontainers.demo;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.reactive.function.client.WebClient;
import org.springframework.web.reactive.function.client.support.WebClientAdapter;
import org.springframework.web.service.invoker.HttpServiceProxyFactory;

@Configuration
public class AppConfig {

  @Bean
  public PhotoServiceClient photoServiceClient(
    @Value("${photos.api.base-url}") String photosApiBaseUrl
  ) {
    WebClient client = WebClient.builder().baseUrl(photosApiBaseUrl).build();
    HttpServiceProxyFactory factory = HttpServiceProxyFactory
      .builder(WebClientAdapter.forClient(client))
      .build();
    return factory.createClient(PhotoServiceClient.class);
  }
}
```

La URL base del servicio de fotos se externaliza como una propiedad de
configuración. Agrega la siguiente entrada a
`src/main/resources/application.properties`:

```properties
photos.api.base-url=https://jsonplaceholder.typicode.com
```

## Crear el endpoint de la API REST

Crea `AlbumController.java`:

```java
package com.testcontainers.demo;

import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.reactive.function.client.WebClientResponseException;

@RestController
@RequestMapping("/api")
class AlbumController {

  private static final Logger logger = LoggerFactory.getLogger(
    AlbumController.class
  );

  private final PhotoServiceClient photoServiceClient;

  AlbumController(PhotoServiceClient photoServiceClient) {
    this.photoServiceClient = photoServiceClient;
  }

  @GetMapping("/albums/{albumId}")
  public ResponseEntity<Album> getAlbumById(@PathVariable Long albumId) {
    try {
      List<Photo> photos = photoServiceClient.getPhotos(albumId);
      return ResponseEntity.ok(new Album(albumId, photos));
    } catch (WebClientResponseException e) {
      logger.error("Failed to get photos", e);
      return new ResponseEntity<>(e.getStatusCode());
    }
  }
}
```

Este endpoint llama al servicio de fotos para un ID de álbum específico y
devuelve una respuesta como:

```json
{
  "albumId": 1,
  "photos": [
    {
      "id": 51,
      "title": "non sunt voluptatem placeat consequuntur rem incidunt",
      "url": "https://via.placeholder.com/600/8e973b",
      "thumbnailUrl": "https://via.placeholder.com/150/8e973b"
    },
    {
      "id": 52,
      "title": "eveniet pariatur quia nobis reiciendis laboriosam ea",
      "url": "https://via.placeholder.com/600/121fa4",
      "thumbnailUrl": "https://via.placeholder.com/150/121fa4"
    }
  ]
}
```

