# Escribir pruebas con Testcontainers MockServer


Simular las interacciones de API externas a nivel del protocolo HTTP, en lugar de
simular los métodos de Java, te permite verificar el comportamiento de serialización
y deserialización, así como simular problemas de red.

Testcontainers proporciona un módulo MockServer que inicia una instancia de
[MockServer](https://www.mock-server.com/) dentro de un contenedor Docker.
Luego, puedes utilizar `MockServerClient` para configurar las expectativas simuladas.

## Escribir la prueba

Crea `AlbumControllerTest.java`:

```java
package com.testcontainers.demo;

import static io.restassured.RestAssured.given;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.Matchers.hasSize;
import static org.mockserver.model.HttpRequest.request;
import static org.mockserver.model.HttpResponse.response;
import static org.mockserver.model.JsonBody.json;

import io.restassured.RestAssured;
import io.restassured.http.ContentType;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockserver.client.MockServerClient;
import org.mockserver.model.Header;
import org.mockserver.verify.VerificationTimes;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.web.server.LocalServerPort;
import org.springframework.test.context.DynamicPropertyRegistry;
import org.springframework.test.context.DynamicPropertySource;
import org.testcontainers.mockserver.MockServerContainer;
import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers;

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@Testcontainers
class AlbumControllerTest {

  @LocalServerPort
  private Integer port;

  @Container
  static MockServerContainer mockServerContainer =
    new MockServerContainer("mockserver/mockserver:5.15.0");

  static MockServerClient mockServerClient;

  @DynamicPropertySource
  static void overrideProperties(DynamicPropertyRegistry registry) {
    mockServerClient =
    new MockServerClient(
      mockServerContainer.getHost(),
      mockServerContainer.getServerPort()
    );
    registry.add("photos.api.base-url", mockServerContainer::getEndpoint);
  }

  @BeforeEach
  void setUp() {
    RestAssured.port = port;
    mockServerClient.reset();
  }

  @Test
  void shouldGetAlbumById() {
    Long albumId = 1L;

    mockServerClient
      .when(
        request().withMethod("GET").withPath("/albums/" + albumId + "/photos")
      )
      .respond(
        response()
          .withStatusCode(200)
          .withHeaders(
            new Header("Content-Type", "application/json; charset=utf-8")
          )
          .withBody(
            json(
              """
              [
                   {
                       "id": 1,
                       "title": "accusamus beatae ad facilis cum similique qui sunt",
                       "url": "https://via.placeholder.com/600/92c952",
                       "thumbnailUrl": "https://via.placeholder.com/150/92c952"
                   },
                   {
                       "id": 2,
                       "title": "reprehenderit est deserunt velit ipsam",
                       "url": "https://via.placeholder.com/600/771796",
                       "thumbnailUrl": "https://via.placeholder.com/150/771796"
                   }
               ]
              """
            )
          )
      );

    given()
      .contentType(ContentType.JSON)
      .when()
      .get("/api/albums/{albumId}", albumId)
      .then()
      .statusCode(200)
      .body("albumId", is(albumId.intValue()))
      .body("photos", hasSize(2));

    verifyMockServerRequest("GET", "/albums/" + albumId + "/photos", 1);
  }

  @Test
  void shouldReturn404StatusWhenAlbumNotFound() {
    Long albumId = 1L;
    mockServerClient
      .when(
        request().withMethod("GET").withPath("/albums/" + albumId + "/photos")
      )
      .respond(response().withStatusCode(404));

    given()
      .contentType(ContentType.JSON)
      .when()
      .get("/api/albums/{albumId}", albumId)
      .then()
      .statusCode(404);

    verifyMockServerRequest("GET", "/albums/" + albumId + "/photos", 1);
  }

  private void verifyMockServerRequest(String method, String path, int times) {
    mockServerClient.verify(
      request().withMethod(method).withPath(path),
      VerificationTimes.exactly(times)
    );
  }
}
```

Esto es lo que hace la prueba:

- `@SpringBootTest` inicia la aplicación completa en un puerto aleatorio.
- Las anotaciones `@Testcontainers` y `@Container` inician un
  `MockServerContainer` y crean un `MockServerClient` conectado a él.
- `@DynamicPropertySource` sobrescribe `photos.api.base-url` para que apunte al
  endpoint de MockServer, por lo que la aplicación se comunica con MockServer en
  lugar de con el servicio de fotos real.
- `@BeforeEach` reinicia el `MockServerClient` antes de cada prueba para que las
  expectativas de una prueba no afecten a otra.
- `shouldGetAlbumById()` configura una respuesta simulada para
  `/albums/{albumId}/photos`, envía una solicitud al endpoint
  `/api/albums/{albumId}` de la aplicación y verifica el cuerpo de la respuesta.
  También utiliza `mockServerClient.verify()` para confirmar que la llamada
  esperada a la API llegó a MockServer.
- `shouldReturn404StatusWhenAlbumNotFound()` configura MockServer para devolver
  un estado 404 y verifica que la aplicación propague ese estado a quien realiza
  la llamada.

