Pruebas de unidad e integración en un proyecto Spring Boot

Pruebas de unidad e integración en un proyecto Spring Boot

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

Publicado en julio 8, 2020

,

,

,

,

,

,

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

« »