Compartir comentarios
Las respuestas se generan en base a la documentación.

Crear el proyecto Spring Boot

Configurar el proyecto

Crea un proyecto Spring Boot desde Spring Initializr seleccionando los starters Spring Web, Spring Reactive Web y Testcontainers.

Alternativamente, puedes clonar el repositorio de la guía.

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

<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 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 es una biblioteca para simular servicios basados en HTTP. Testcontainers proporciona un módulo de MockServer que ejecuta MockServer como un contenedor Docker.

Crear los modelos Album y Photo

Crea Album.java utilizando Java records:

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. Crea una interfaz con un método que obtenga las fotos para un ID de álbum determinado:

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:

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:

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

Crear el endpoint de la API REST

Crea AlbumController.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:

{
  "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"
    }
  ]
}