Este artículo continua y finaliza la serie iniciada en el primer artículo.
En este artículo vamos a ver cómo implementar tests en la aplicación. Para ello, antes me gustaría comentar la existencia, por si no se conociera, de métodos de desarrollo dirigido a test, donde los tests forman la parte esencial del desarrollo y donde este artículo cobra más importancia si cabe.
Además, en el desarrollo de una aplicación, hay diferentes tipos de pruebas. Dentro de las funcionales, distinguimos, por ejemplo:
- Pruebas unitarias
- Pruebas de componentes
- Pruebas de integración
- Pruebas de sistema
Existiendo, además, otros tipos de pruebas no funcionales como las de estrés (para lo cual podríamos ayudarnos, por ejemplo de JMeter). Para la implementación de las diferentes pruebas existen diferentes tecnologías y herramientas. En este artículo nosotros utilizaremos las herramientas de JUnit y SpringFramework para el desarrollo de pruebas unitarias y de integración. En el proyecto, todo lo referente a tests está en la carpeta de tests.
Tests de unidad
Estos test son responsables de probar una unidad funcional y aislada de código. Estos tests están bajo la carpeta unit. Por ejemplo, podemos ver el test de unidad del servicio de usuarios. En dicha clase destacamos:
- Activación de perfil de test:
@ActiveProfiles("test")
De esta manera activamos el perfil de maven «test«, que nos permite configurar ciertas propiedades del proyecto que nos faciliten las pruebas. Por ejemplo, evitar el envío real de correos.
- Mockear clases o métodos:
@MockBean private UserRepository userRepository;
@MockBean es una anotación para mockear una clase. En otras palabras, sustituimos la clase/métodos reales por implementaciones propias ad-hoc para los tests. La principal utilidad de estos mocks es para evitar ejecutar y probar otras secciones de código fuera de la unidad en pruebas. De esta manera, por ejemplo cuando hacemos:
when(this.userRepository.findByUsername(TestUtil.VALID_USER_USERNAME)).thenReturn(userActive);
indicamos al repositorio que, cuando busquemos al usuario VALID_USER_USERNAME, devolvamos el userActive, evitando de esta manera ir a base de datos, pues el repositorio de usuario ya tiene sus propios tests que comprueban su correcto funcionamiento.
- Precedencia de ejecución: Los métodos precedidos por la anotación @Before se ejecutarán siempre antes de cada test.
- @Test es una anotación que marca los tests a ejecutar. Cada test definirá el comportamiento que considera correcto a través de aserciones como
assertEquals(user.getUsername(), TestUtil.VALID_USER_USERNAME);
o esperando excepciones:
@Test(expected = InstanceNotFoundException.class)
Tests de integración
Estos test son responsables de probar la integración/comunicación entre diferentes unidads funcionales. Estos tests están bajo la carpeta integration. Para todos estos tests se levanta un API local y se pasa por todas las capas necesarias para el test, probando de esta manera, a su vez, controlador, servicios, repositorios y cualquier otra lógica implicada.
Por ejemplo, podemos ver el test de integración de la parte de autenticación del API. En estos tests lo que se hace es probar la autenticación para diferentes casos:
Caso 1
@Test public void authLoginOk() throws Exception
Comprueba que la autenticación es correcta cuando el usuario y la contraseña son correctos.
Consideramos que la autenticación fue correcta cuando:
- recibimos una respuesta con estado HTTP 2xx:
.andExpect(status().isOk())
- recibimos un un access token y un refresh token
.andExpect(jsonPath("$.access_token", is(notNullValue()))) .andExpect(jsonPath("$.refresh_token", is(notNullValue())))
- la expiración del access token es correcta
.andExpect(jsonPath("$.expires_in", is(Long.valueOf(JwtTokenUtil.JWT_ACCESS_TOKEN_VALIDITY_IN_SECONDS).intValue())))
- el scope del access_token es el esperado
.andExpect(jsonPath("$.scope", equalTo(UserDetailsService.getScopeFromRole(role))))
- es un token de tipo Bearer
.andExpect(jsonPath("$.token_type", is(JwtTokenUtil.TOKEN_TYPE_BEARER)));
Caso 2
@Test public void authLoginWrongUsername() throws Exception
Comprueba que la autenticación falla con un usuario no existente.
Caso 3
@Test public void authLoginWrongPassword() throws Exception
Comprueba que la autenticación falla con un usuario existente pero con contraseña errónea.
Caso 4
@Test public void authRefreshToken() throws Exception
Comprueba que la autenticación es correcta a través de un refresh token correcto.
Caso 5
@Test public void userForbiddenResource() throws Exception
Comprueba que la autorización falla con un token correcto pero con un scope no adecuado para el recurso solicitado.
Ejecución de tests
Estos tests están integrados en el ciclo de vida de la aplicación a través de maven. De esta manera, por ejemplo al hacer un empaquetado, se ejecutarán los tests:
mvn package
Si quisiéramos evitar la ejecución de los tests, podríamos hacerlo a través de
mvn -Dmaven.test.skip=true package
Para ejecutar los test de unidad explícitamente, podríamos hacer:
mvn test
O para ejecutar únicamente uno en particular:
mvn -Dtest=ApiToursControllerIT test
O para ejecutar los test de integración:
mvn integration-test
Imagen principal: Photo by Glenn Carstens-Peters on Unsplash
« Utilidades comunes en un proyecto Spring Boot Evolución tecnológica y uso de internet en España en 2019 »