El objetivo de este blog es explicar cómo podemos implementar tests unitarios para distintos componentes de nuestra aplicación desarrollada en Spring Boot.
Conceptos básicos Spring Boot
- Datos de pruebas: en este contexto se consideran datos de prueba aquellos que inicializamos (generamos instancias de objetos) para poder probar nuestros métodos. Esta inicialización se puede realizar de forma manual o mediante una librería de utilidad. Se recomienda hacer uso de estas últimas y sólo asignar de forma manual aquellos datos que tengan un interés especial.
- Mocks: un mock es una “instancia tonta” de la clase. Esta nos permite definir comportamientos explícitos para cada interacción que tenga.
- Capturadores: elementos que nos permiten capturar un valor definido en un comportamiento de un mock (como parámetro) para luego poder realizar validaciones sobre este.
Tests según la naturaleza de la clase
Controlador
Se recomienda que los tests sobre controladores utilicen la anotación @WebMvcTest además de extender de una clase abstracta común en caso de tener configuraciones adicionales relacionadas con filtros / seguridad / interceptores. Con esto conseguimos que la clase de test solo levante el contexto de spring con el controlador y los posibles elementos adicionales anteriormente mencionados. Todos aquellos beans que sean deban ser sustituidos por mocks se anotarán con @MockBean. Finalmente haremos uso de la clase MockMvc para definir el comportamiento de la llamada así como definir validaciones sobre todas las partes de la respuesta que queramos.
Generalmente cada test contará de las siguientes partes:
- Inicialización de los datos y comportamientos de pruebas
- Llamada al endpoint deseado del controlador y validación de la respuesta
- Validación de interacciones con otros elementos y captura de valores
- Validación de valores capturados
Servicio / Componente
Por norma general para generar los tests unitarios sobre los servicios y componentes simplemente deberemos de definir tests que funcionen con la extensión de Mockito ( @ExtendWith(MockitoExtension.class) ). Estos tests no levantan ningún contexto de Spring por lo que su tiempo de ejecución por norma general es muy rápido. En este tipo de tests los mocks deben de inicializarse con la anotación de @Mock mientras que la clase sobre la que realizamos los tests debe de anotarse con @InjectMocks .
Generalmente cada test contará de las siguientes partes:
- Inicialización de los datos y comportamientos de pruebas
- Llamada al método deseado que se desea testear
- Validación de interacciones con otros elementos y captura de valores
- Validación de valores capturados y de respuesta del método
Ejemplo
Client Rest
Dado que estas clases son las encargadas de interactuar con otras apis, es importante que nuestros casos de test cubran todas las posibles respuestas que podamos tener.
Para indicar que vamos a testear un cliente test debemos de anotar los tests con @RestClientTest además de anotarlo con @AutoConfigureWebClient (registerRestTemplate = true) si queremos que también nos inicialice el RestTemplate. Esto permite que solo se levante la parte del contexto de Spring Boot relacionada con estos componentes.
Todos aquellos beans que sean deban ser sustituidos por mocks se anotarán con @MockBean. Además todas aquellas propiedades que se usen dentro de la clase sobre pruebas también deberán de inicializarse mediante la anotación @EnableConfigurationProperties.
Finalmente en cada test simularemos el comportamiento de la api externa mediante el uso de la clase MockRestServiceServer .
Dado que generalmente las respuestas pueden ser bastante grandes se recomienda extraer cada una de ellas en archivos independientes de forma ordenada dentro de la carpeta test/resources y en cada test hacer uso de un método estático de utilidad para cargar el contenido de dicho archivo.
Generalmente cada test contará de las siguientes partes:
- Inicialización del objeto de request
- Definición del comportamiento del falso servidor haciendo uso del request y response predefinidos
- Llamada al método deseado que se desea testear
- Validación de interacciones con otros elementos y captura de valores
- Validación de valores capturados y de respuesta del método
Conceptos intermedios
Objetos de prueba
Como ya hemos mencionado anteriormente se recomienda hacer uso de alguna librería de generación automática de dichos objetos. Las características principales a tener en cuenta para elegir dicha librería son:
- Posibilidad de generar subobjetos
- Soportar las anotaciones de validación de java para no generar datos no válidos
Se recomienda encapsular el uso de dicha librería dentro de una clase estática propia, de esta forma en caso de tener que sustituirla la refactorización sería mucho más sencilla. Adicionalmente en dicha clase podemos tener distintos métodos para poder generar objetos de prueba con estrategias distintas.
Finalmente, si tenemos que generar objetos que cumplan ciertas características, por ejemplo que una variable tenga cierto valor, etc. y además es muy probable que dicho objeto sea utilizado por distintos tests en distintas clases de tests, entonces se recomienda que la instanciación de dichos objetos también se extraiga en otra clase o clases abstractas, si puede ser haciendo uso del patrón ObjectMother
Test con múltiples casos
En ocasiones podemos encontrarnos en la situación en la cual queremos validar el comportamiento de un método para un conjunto de valores esperando el mismo tipo de comportamiento en el resultado. Un caso muy común es validar algún tipo de cálculo o fórmula matemática. Para ello en lugar de definir muchos tests repetitivos (básicamente haremos copy/paste cambiando los parámetros y el resultado) podemos definir un test parametrizado. Los test parametrizados pueden tener diversos orígenes de datos, los casos más comunes son:
- Valores únicos en anotación
- Valores csv en anotación
- Valores csv en archivo
- Valores en otro método
Links de interés
Explicación detallada del patrón ObjectMother del blog de Martin Fowler
https://martinfowler.com/bliki/ObjectMother.html
Explicación del patrón ObjectMother
https://java-design-patterns.com/patterns/object-mother/
¡Habla con nosotros!
Author