Testing en aplicaciones con Quarkus

Testing en Quarkus

Testing en Quarkus


Como ya hemos visto en entradas anteriores, Quarkus es un framework totalmente orientado al cloud. En este artículo vamos a ver como podemos hacer testing en aplicaciones con Quarkus, ya que no podemos olvidarnos de realizar testing en nuestras aplicaciones para evitar errores.

Si quieres ver otros artículos sobre testing en Spring Boot puedes echar un ojo aquí.

Configuración de una aplicación Quarkus para testing

Vamos a partir de una apliación anterior en la que vamos a añadir configuración para hacer un buen testing de nuestra aplicación. Puedes hacer un git clone con esta url, https://github.com/refactorizando-web/quarkus-testing

Este proyecto expone un simple endpoint de entrada a la aplicación con una conexión a una Base de Datos H2. El proyecto se divide en 3 capas que veremos a continuación.

Para poder hacer test sobre nuestro servicio creado en Quarkus vamos a añadir, entre otras, las siguientes dos dependencias:

<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-junit5-mockito</artifactId>
</dependency>
<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-test-h2</artifactId>
</dependency>

Creación de proyecto en Quarkus para testing

Para poder realizar testing en aplicaciones con Quarkus vamos a mostrar una pequeña apliación.

Crear Resource en Quarkus

A continuación vamos a crear un proyecto simple con 3 capas para poder realizar test integrados y unitarios sobre ellos:

@Path("/cars")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
public class CarResource {

    @Inject
    CarService carService;

    @GET
    @Path("/model")
    public Set<CarEntity> findModel(@QueryParam("query") String query) {
        return carService.find(query);
    }

}

Crear servicio en Quarkus

Vamos a crear la capa Service que será la encargada de realizar la lógica de negocio de nuestra aplicación. Esta capa mediante inyección de dependencias haciendo uso de @Inject se comunicará con el repositorio.

@Transactional
@ApplicationScoped
public class CarService {

    @Inject
    CarRepository carRepository;

    public Set<CarEntity> find(String query) {
        if (query == null) {
            return carRepository.findAll().stream().collect(toSet());
        }

        return carRepository.findBy(query).collect(toSet());
    }

}

Crear capa Repository en Quarkus

Nuestra capa repository será la que se comunique directamente con nuestra BBDD, en este caso es una base de datos en memoria (H2). Para realizar la búsqueda, vamos a realizar una query a mano en nuestra capa repository. Una de las funcionalidades de Quarkus es que directamente nos va a devolver un Stream.

@ApplicationScoped
public class CarRepository implements PanacheRepository<CarEntity> {

    public Stream<CarEntity> findBy(String query) {

        return find("color like :query or model like :query", with("query", "%"+query+"%")).stream();
    }

}

Añadir datos en tiempo de test con @Alternative

Cuando hacemos tests vamos a querer tener datos en nuestra Base de Datos, esta funcionalidad en Quarkus la podemos hacer haciendo uso de @Alternative para proporcionar un bean para cargar datos en nuestra aplicación.

@Priority(1)
@Alternative
@ApplicationScoped
public class TestCarRepository extends CarRepository {

    @PostConstruct
    public void init() {
        persist(new CarEntity("Mustang", "Blue"),
          new CarEntity("AudiA6", "Grey"));
    }

}

Aunque en nuestro código ya tengamos una implementación del Repository con las anotaciones @Priority(1) y @Alternative en tiempo de test se sobreescribirá con nuestra nueva clase. Estas anotaciones nos pueden ayudar para crear casos de pruebas.

Testing en Quarkus haciendo uso de Mocks

Quarkus nos ofrece la anotación @QuarkusMock para poder hacer mocks en nuestros test. El uso de mock es una de las herramientas más comunes cuando trabajamos con mocks.

Uso de QuarkusMock

La librería de QuarkusMock puede ser usada para simular temporalmente cualquier bean. Al usar este método con @BeforeAll tendrá efecto en todas las clases en cambio, podemos aplicarlo a nivel de método usándolo únicamente en un método.

@QuarkusTest
class CarServiceInjectMockUnitTest {

    @Inject
    CarService carService;

    @InjectMock
    CarRepository carRepository;

    @BeforeEach
    void setUp() {
        when(carRepository.findBy("yellow"))
          .thenReturn(Arrays.stream(new CarEntity[] {
            new CarEntity("Megane", "yellow"),
            new CarEntity("A6", "yellow")}));
    }

    @Test
    void whenGetCarByModel_thenCarsAreReturned() {
        assertEquals(2, carService.find("yellow").size());
    }

}

En el ejemplo anterior lo que hacemos es inyectar el mock que hemos definido con @Alternative cuando nuestra clase repository principal es invocada.

Uso de InjectMock en nuestros test con Quarkus

Una de las funcionalidades que nos ofrece Mockito es hacer uso de una «simulación» de inyección de dependencias haciendo uso de @InjectMock, de manera que en lugar de usar QuarkusMock hacemos uso de la anotación @InjectMock y tendríamos nuestra clase inyectada en el contexto de test.

@QuarkusTest
class CarServiceQuarkusMockUnitTest {

    @Inject
    CarService carService;

    @BeforeEach
    void setUp() {
        CarRepository mock = Mockito.mock(TestCarRepository.class);
        Mockito.when(mock.findBy("grey"))
          .thenReturn(Arrays.stream(new CarEntity[] {
            new CarEntity("A6", "grey"),
            new CarEntity("I30", "grey"),
            new CarEntity("Toledo", "grey")}));
        QuarkusMock.installMockForType(mock, CarRepository.class);
    }

    @Test
    void whenFindByModel_thenCarsAreReturned() {
        assertEquals(3, carService.find("grey").size());
    }

}

Usos de Spy con Quarkus

Cuando hacemos uso de mock lo que hacemos es sustituir los objetos por completo, en cambio con Spy lo que hacemos es mantener los objetos originales y reemplarzar algunos métodos. Para aplicar esta funcionalidad hacemos uso de @InjectSpy.

@QuarkusTest
class CarSpyIntegrationTest {

    @InjectSpy
    CarService carService;

    @Test
    void whenGetCarsByModel_thenModelIsReturned() {
        given().contentType(ContentType.JSON).param("query", "Mustang")
          .when().get("/cars/model")
          .then().statusCode(200);

        verify(carService).find("Mustang");
    }

}

Test integrados con Quarkus

Quarkus nos proporciona la anotación @QuarkusTest, para inyectar el contexto en la parte de testing. Esta anotación deberá ir en la parte superior de la clase.

@QuarkusTest
class CarResourceIntegrationTest {

    @Test
    void whenGetCarsByModel_thenCarsAreReturned() {
        given().contentType(ContentType.JSON).param("query", "Mustang")
          .when().get("/cars/model")
          .then().statusCode(200)
          .body("size()", is(1))
          .body("model", hasItem("Mustang"))
          .body("color", hasItem("Blue"));
    }

}

Con @QuarkusTest vamos a incorporar rest-assured como una manera conveniente de hacer testing sobre endpoints http.

Uso de @TestHTTPResource para inyectar URLs

Cuando queremos hacer un test integrado debemos invocar a un endpoint en concreto. Esto, lo podemos hacer definiendo el endpoint como una constante o haciendo uso de @TestHTTPResource para inyectar la URL.

@TestHTTPResource("/cars/model")
URL carEndpoint; 

Test con @TestHTTPEndpoint

@TestHTTPEndpoint nos va a a permitir mantener la url de nuestro servicio, es decir, aunque cambiemos el path de nuestra clase, no necesitaremos hacer cambios en nuestros tests.

    @TestHTTPEndpoint(CarResource.class)
    @TestHTTPResource("model")
    URL carEndpoint;

Testing en Quarkus con Inject

Quarkus nos va a permitir hacer inyección de dependencias en nuestros test a través de la anotación @Inject.

Uso de pérfiles para Test

Al igual que otros frameworks, por ejemplo con Spring Boot, podemos realizar la configuración de perfiles para usarlos en nuestros test.

Para realizar la configuración y uso de perfiles para test, vamos a implementar la clase QuarkusTestProfile.

Para comenzar definimos nuestra clase que va a hacer uso de QuarkusTestProfile:

public class TestProfileCustom implements QuarkusTestProfile {

    @Override
    public Map<String, String> getConfigOverrides() {
        return Collections.singletonMap("quarkus.resteasy.path", "/custom");
    }

    @Override
    public Set<Class<?>> getEnabledAlternatives() {
        return Collections.singleton(TestCarRepository.class);
    }

    @Override
    public String getConfigProfile() {
        return "custom-profile";
    }

}

La clase anterior va a sobreescribir los métodos dados por la clase QuarkusTestProfile. En la que indicamos las soluciones alternativas y la configuración del perfil en donde lo vamos a realizar.

Una vez hemos definido nuestra clase anterior tendremos que realizar la definición en nuestro fichero de propiedades para indicar que acción tomar en el perfil que acabamos de crear.

%custom-profile.quarkus.datasource.jdbc.url = jdbc:h2:file:./testdb
@QuarkusTest
@TestProfile(TestProfileCustom.class)
class CarResourceITest {

    public static final String CAR_WORKSHOP = "/custom/cars/model";

    @Test
    void whenGetCars_thenAllCarsAreReturned() {
        given().contentType(ContentType.JSON)
          .when().get(CAR_WORKSHOP)
          .then().statusCode(200)
          .body("size()", is(2))
          .body("model", hasItems("AudiA6", "Mustang"));
    }
}

Testing en Quarkus de imagenes nativas

Ya que Quarkus es un framework Cloud Native y orientado a imágenes nativas, lo normal sería poder hacer testing sobre este tipo de imágenes.

Para poder utilizar imágenes nativas con Quarkus vamos a hacer uso de la anotación @NativeImageTest

@NativeImageTest
@QuarkusTestResource(H2DatabaseTestResource.class)
class NativeLibraryResourceIT extends LibraryHttpEndpointIntegrationTest {
}

La anotación @QuarkusTestResource le indica a Quarkus que realice las operaciones necesarias para hacer uso de la imagen nativa.

Para poder construir la imagen y comenzar nuestros test vamos a hacer uso del Profile native:

mvn verify -Pnative

Hay que tener en cuenta que actualmente solo funciona de manera nativa lo que es la aplicación, las inyecciones no funcionan en modo nativo.

Conclusión

En esta entrada hemos visto como hacer testing en aplicaciones con Quarkus, algo esencial cuando creamos aplicaciones o servicios. Crear test es algo esencial e imprescindible para asegurar nuestro código aportando pruebas de su funcionamiento y asegurando las funcionalidades.

Si necesitas más información puedes escribirnos un comentario o un correo electrónico a refactorizando.web@gmail.com o también nos puedes contactar por nuestras redes sociales Facebook o twitter y te ayudaremos encantados!



Deja una respuesta

Tu dirección de correo electrónico no será publicada.