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

Escribe pruebas con Testcontainers

Para probar la aplicación, necesitas una instancia de LocalStack en ejecución que emule los servicios S3 y SQS de AWS. Testcontainers levanta LocalStack en un contenedor Docker y @DynamicPropertySource lo conecta a Spring Cloud AWS.

Configurar el contenedor de pruebas

Puedes iniciar un contenedor de LocalStack y configurar las propiedades de Spring Cloud AWS para comunicarse con él en lugar de los servicios reales de AWS. Las propiedades que debes configurar son:

spring.cloud.aws.s3.endpoint=http://localhost:4566
spring.cloud.aws.sqs.endpoint=http://localhost:4566
spring.cloud.aws.credentials.access-key=noop
spring.cloud.aws.credentials.secret-key=noop
spring.cloud.aws.region.static=us-east-1

Para las pruebas, utiliza un contenedor efímero que se inicie en un puerto aleatorio disponible para que puedas ejecutar múltiples compilaciones en CI en paralelo sin conflictos de puertos.

Escribir la prueba

Crea MessageListenerTest.java:

package com.testcontainers.demo;

import static org.assertj.core.api.Assertions.assertThat;
import static org.awaitility.Awaitility.await;
import static org.testcontainers.containers.localstack.LocalStackContainer.Service.S3;
import static org.testcontainers.containers.localstack.LocalStackContainer.Service.SQS;

import java.io.IOException;
import java.time.Duration;
import java.util.UUID;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.DynamicPropertyRegistry;
import org.springframework.test.context.DynamicPropertySource;
import org.testcontainers.containers.localstack.LocalStackContainer;
import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers;
import org.testcontainers.utility.DockerImageName;

@SpringBootTest
@Testcontainers
class MessageListenerTest {

  @Container
  static LocalStackContainer localStack = new LocalStackContainer(
    DockerImageName.parse("localstack/localstack:3.0")
  );

  static final String BUCKET_NAME = UUID.randomUUID().toString();
  static final String QUEUE_NAME = UUID.randomUUID().toString();

  @DynamicPropertySource
  static void overrideProperties(DynamicPropertyRegistry registry) {
    registry.add("app.bucket", () -> BUCKET_NAME);
    registry.add("app.queue", () -> QUEUE_NAME);
    registry.add(
      "spring.cloud.aws.region.static",
      () -> localStack.getRegion()
    );
    registry.add(
      "spring.cloud.aws.credentials.access-key",
      () -> localStack.getAccessKey()
    );
    registry.add(
      "spring.cloud.aws.credentials.secret-key",
      () -> localStack.getSecretKey()
    );
    registry.add(
      "spring.cloud.aws.s3.endpoint",
      () -> localStack.getEndpointOverride(S3).toString()
    );
    registry.add(
      "spring.cloud.aws.sqs.endpoint",
      () -> localStack.getEndpointOverride(SQS).toString()
    );
  }

  @BeforeAll
  static void beforeAll() throws IOException, InterruptedException {
    localStack.execInContainer("awslocal", "s3", "mb", "s3://" + BUCKET_NAME);
    localStack.execInContainer(
      "awslocal",
      "sqs",
      "create-queue",
      "--queue-name",
      QUEUE_NAME
    );
  }

  @Autowired
  StorageService storageService;

  @Autowired
  MessageSender publisher;

  @Autowired
  ApplicationProperties properties;

  @Test
  void shouldHandleMessageSuccessfully() {
    Message message = new Message(UUID.randomUUID(), "Hello World");
    publisher.publish(properties.queue(), message);

    await()
      .pollInterval(Duration.ofSeconds(2))
      .atMost(Duration.ofSeconds(10))
      .ignoreExceptions()
      .untilAsserted(() -> {
        String msg = storageService.downloadAsString(
          properties.bucket(),
          message.uuid().toString()
        );
        assertThat(msg).isEqualTo("Hello World");
      });
  }
}

Esto es lo que hace la prueba:

  • @SpringBootTest inicia el contexto completo de la aplicación Spring.
  • Las anotaciones @Testcontainers y @Container de JUnit 5 de Testcontainers gestionan el ciclo de vida de la instancia de LocalStackContainer.
  • @DynamicPropertySource obtiene las URLs dinámicas de los endpoints de S3 y SQS, la región, la clave de acceso (access key) y la clave secreta (secret key) del contenedor, y las registra como propiedades de configuración de Spring Cloud AWS.
  • @BeforeAll crea la cola SQS y el bucket S3 requeridos usando la herramienta de CLI awslocal que viene preinstalada en la imagen Docker de LocalStack. La API localStack.execInContainer() ejecuta comandos dentro del contenedor.
  • shouldHandleMessageSuccessfully() publica un Message en la cola SQS. El listener recibe el mensaje y almacena su contenido en el bucket S3 con el UUID como clave. Awaitility espera hasta 10 segundos para que aparezca el contenido esperado en el bucket.