# 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:

```properties
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`:

```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.

