domingo, 7 de enero de 2024

Wiremock y Quarkus: Como configurarlo

Wiremock es una gran herramienta que puede ayudarte en la realización de pruebas de calidad de tu código. Un paso fundamental a la hora de desarrollar pruebas de aplicaciones o servicios que invocan a otros. Las pruebas son un paso fundamental en el desarrollo de software. Y Quarkus es un framework Java diseñado especialmente para el desarrollo de aplicaciones en contenedores, sin servidor y en la nube, especialmente para Kubernetes. De ambos hemos hablado varias veces ya.

Hoy veremos cómo podemos usarlos juntos, puesto que su uso no es tan sencillo, como pudiera ser con otra aplicación realizada con otro framework Java. Y para lograrlo haremos uso de la anotación @QuarkusTestResource. En una clase de testing debemos añadir dicha anotación asociada a una clase que implemente a su vez la interfaz QuarkusTestResourceLifecycleManager. Esto nos permitirá inicializar un servicio externo antes de la ejecución de las propias pruebas. En nuestro caso, nos permitirá arrancar el servidor Wiremock.

public class WiremockTestResource implements QuarkusTestResourceLifecycleManager {
  public static WireMockServer wireMockServer;
  @Override
  public Map<String, String> start() {
    wireMockServer = new WireMockServer(57001);
    wireMockServer.start();
    return new HashMap<String, String>();
  }
  @Override
  public void stop() {
    if (wireMockServer != null) {
      wireMockServer.stop();
    }
  }
}

Una vez creada la clase que permite arrancar Wiremock, haremos uso de ella a través de la anotación @QuarkusTestResource. De esta forma ya solo nos quedará indicar los stubs asociados a nuestros tests.

@QuarkusTest
@TestHTTPEndpoint(WiremockResource.class) //Resource that call external service
@QuarkusTestResource(WiremockTestResource.class)
public class WiremockResourceBasicTest {
  @Test
  public void getById() {
    Log.info("WiremockResourceBasicTest - getById");
    ObjectMapper mapper = new ObjectMapper();
    Movie movie = new Movie(1L, "Denis Villeneuve", "Dune");
    Log.info("WM port: " + WiremockTestResource.wireMockServer.getOptions().portNumber());
    WiremockTestResource.wireMockServer.stubFor(
      WireMock.get("/imdb/film/1").willReturn(WireMock.aResponse()
      .withJsonBody(mapper.valueToTree(movie))
      .withStatus(200).withHeader(HttpHeaders.CONTENT_TYPE, "application/json")));
    when().get("/1").andReturn().then().statusCode(200)
      .body("id", is(1)).body("name", is("Dune")).body("director",
      is("Denis Villeneuve"));
  }
}

Como veis es muy sencillo. Ahora veremos una opción un poco más complejas pero que permitirá crear el servidor de Wiremock con más opciones de configuración y modificarlas dinámicamente. Aunque el desarrollo es un poco lioso, debido a que tiene referencias cruzadas. 

Por un lado crearemos una anotación que tenga las propiedades que queramos modificar dinámicamente posteriormente a través de la propia anotación. Pero también le añadiremos la anotación @QuarkusTestResource que hará referencia a la clase que controla el servidor de Wiremock. 

@QuarkusTestResource(value = WiremockResourceConfigurable.class, restrictToAnnotatedClass = true)
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface WiremockTestAnnotation {
    String port() default "57005";
}

Ahora mostraremos la clase que configura el servidor de Wiremock. Será similar al ejemplo anterior, pero con tres añadidos. Para empezar, la interfaz que vamos a implementar será QuarkusTestResourceConfigurableLifecycleManager, y está estará asociada a la anotación creada anteriormente. Luego, a través de los atributos de la anotación, en el método init, podremos configurar dinámicamente el servidor. Y por último, con el retorno del método start, también podremos sobre escribir valores del fichero application.properties con los valores dinámicos. En este caso, a través de esta sobre escritura, podremos modificar el puerto del servicio externo pre configurado para el cliente.

public class WiremockResourceConfigurable 
  implements QuarkusTestResourceConfigurableLifecycleManager<WiremockTestAnnotation> {	
    public static WireMockServer server;
    private String port;
    @Override
    public void init(WiremockTestAnnotation params) {
        port = params.port();
    }
    @Override
    public Map<String, String> start() {
	server = new WireMockServer(Integer.valueOf(port));
        server.start();
	return  Map.of(
          "quarkus.rest-client.\"com.home.example.service.ExternalService\".url", 
          "http://localhost:"+port
          );
    }
    //...
}

Ya solo nos quedaría mostrar el test, haciendo uso de la anotación que nos permitiría modificar el puerto del servidor wiremock y del servicio externo dinámicamente. Muy similar al anterior. 

@QuarkusTest
@TestHTTPEndpoint(WiremockResource.class)
@WiremockTestAnnotation(port = "57005")
public class WiremockResourceTest {
    @Inject
    @ConfigProperty(name = "quarkus.rest-client.\"com.home.example.service.ExternalService\".url")
    private String serverUrl;
    @Test
    public void getById() {
        Log.info("serverUrl: " + serverUrl);
        ObjectMapper mapper = new ObjectMapper();
        Movie movie = new Movie(1L, "Denis Villeneuve", "Dune");
        WiremockResourceConfigurable.server.stubFor(
            WireMock.get("/imdb/film/1").willReturn(WireMock.aResponse().withJsonBody(mapper.valueToTree(movie))
	    .withStatus(200).withHeader(HttpHeaders.CONTENT_TYPE, "application/json")));	
        when().get("/1").andReturn().then().statusCode(200)
            .body("id", is(1)).body("name", is("Dune")).body("director",is("Denis Villeneuve"));
    }
}

Con esto hemos visto una particularidad más para mejorar los test en Quarkus. Y aunque Wiremock es una gran herramienta que desde hace tiempo es muy util para el desarrollo de pruebas. No quisiera omitir, que también es posible mockear el servicio externo de una forma sencilla con Quarkus.  Y esto es posible haciendo uso de las anotaciones @InjectMock y @RestClient. En el ejemplo que mostramos a continuación, ExternalService ha sido todo el tiempo el cliente web que hemos mockeado con Wiremock.  

@QuarkusTest
@TestHTTPEndpoint(WiremockResource.class)
public class WiremockResourceInjectTest {
    @InjectMock
    @RestClient
    ExternalService extService;
    @Test
    public void getById() {
        Mockito.when(extService.getMovieById(Mockito.anyLong())).thenReturn(new Movie(1L, "Denis Villeneuve", "Dune"));
        when().get("/1").andReturn().then().statusCode(200).body("id", is(1)).body("name", is("Dune")).body("director",
            is("Denis Villeneuve"));
    }
}

Espero que como siempre haya sido util, y si quieres puedes ver el código aquí

No hay comentarios:

Publicar un comentario