Saltar al contenido principal
Página

Tema 4.3 - SpringBoot y pruebas unitarias

SpringBoot usando JUnit y Mockito

Ahora bien, ¿Qué tiene Spring Boot para nuestro tema de pruebas unitarias? Si vemos la configuración del pom.xml del proyecto generado por Spring Initializr, veremos que ya se incluye una dependencia para pruebas, específicamente la que se muestra a continuación.


pom spring con starter de test


Es importante resaltar que el starter spring-boot-starter-test, proporciona diversas librerías útiles para las pruebas como JUnit, Mockito, AssertJ, Hamcrest y demás, también es posible incluir dependencias opcionales, en nuestro proyecto sin especificar la versión, siendo estas:

  • HTMLUnit: Kit de herramientas de prueba para salidas HTML.
  • Selenium: Automatización del navegador para pruebas de UI.
  • Flapdooble: base de datos MongoDB integrada para pruebas.
  •  H2: base de datos SQL integrada para pruebas.
  • Spring REST Docs: genera documentación REST utilizando pruebas automatizadas.


Para nuestro ejemplo, crearemos un proyecto maven llamado springCuadrado desde el servicio web initializr, tal como se hizo anteriormente, incluyendo la dependencia Spring Web; este proyecto, se encargará de hacer varios cálculos para una figura geométrica (Cuadrado), cuenta con una clase cuadradoService y cuadradoController.


La clase cuadradoService contiene el siguiente código

package com.springboot.spring.model;

import org.springframework.stereotype.Component;

@Component
public class CuadradoService {
	
	//Calcula el area de un cuadrado en centímetros cruadrados
	public double calcularArea (double lado) {
		return lado*lado;
	}
	
	//Calcula el perimetro de un cuadrado en centímetros 
	public double calcularPerimetro (double lado) {
		return lado*4;
	}
	
	//Calcula la diagonal de un cuadrado en centímetros 
	public double calcularDiagonal (double lado) {
		return (lado*Math.sqrt(2));
	}
}


Como pueden notar, esta clase se conforma por métodos que permiten calcular el área, perímetro y diagonal de un cuadrado; antes de la clase, hay una anotación @Component, que indica a Spring que la clase anotada (CuadradoService) es uno de sus objetos.

Siguiendo con la presentación de las clases, cuadradoController tiene el siguiente código

package com.springboot.spring.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;

@Controller
public class CuadradoController {
    private CuadradoService cuadradoService;
	
    @Autowired
    public CuadradoController(CuadradoService cuadradoService) {
        this.cuadradoService = cuadradoService;
    }
	
    @PostMapping("/area")
    public ResponseEntity<Double> calcularAreaCuadrado(double lado) {
        return ResponseEntity.ok(cuadradoService.calcularArea(lado));
    }
    
    @PostMapping("/perimetro")
    public ResponseEntity<Double> calcularPerimetroCuadrado(double lado) {
        return ResponseEntity.ok(cuadradoService.calcularPerimetro(lado));
    }
    
    @PostMapping("/diagonal")
    public ResponseEntity<Double> calcularDiagonalCuadrado(double lado) {
        return ResponseEntity.ok(cuadradoService.calcularDiagonal(lado));
    }
}


Vemos que esta clase, esta anotada con @Controller, perteneciente a los estereotipos de Spring, tal como el anterior. Este realiza las tareas de controlador y gestión de la comunicación entre el usuario y el aplicativo, tenemos además dos anotaciones dentro de la clase, la primera es @Autowired, que permite hacer la inyección de dependencias, en este caso entre CuadradoService y CuadradoController.


Nota: Para realizar test, es recomendado realizar el @Autowired a nivel de constructores, como en la clase vista.

Y la segunda anotación es el @PostMapping, utilizada para asignar solicitudes HTTP de tipo POST.


Una vez configuradas las clases vamos a generar las clases Test para cada una, para el caso de la clase CuadradoService, que no tiene ninguna dependencia con otras, utilizaremos JUnit5 el cual se acopla a esta situación, para esta clase, realizamos el test para cada método, por medio de la opción ya vista en capítulos anteriores de JUnit, JUnit Test Case y seguimos las instrucciones dadas, seleccionando así los métodos de esta.


Una vez realizado los pasos y de tener nuestra clase Test, procedemos a añadir nuestro código prueba.


package com.springboot.spring.test;

import static org.junit.jupiter.api.Assertions.*;

import org.junit.jupiter.api.Test;

class CuadradoServiceTest {

	CuadradoService cuadrado = new CuadradoService();
	@Test
	void testCalcularArea() {
		double lado=5;
		double expected = 25;
		double actual = cuadrado.calcularArea(lado);
		
		assertEquals(expected, actual);
	}

	@Test
	void testCalcularPerimetro() {
		double lado=5;
		double expected = 20;
		double actual = cuadrado.calcularPerimetro(lado);
		
		assertEquals(expected, actual);
	}

	@Test
	void testCalcularDiagonal() {
		double lado=5;
		double expected = 7.0710678118654752440084436210485;
		double actual = cuadrado.calcularDiagonal(lado);
		
		assertEquals(expected, actual);
	}

}

 

Solo nos basta correrla y veremos que el resultado que nos arroja JUnit, es exitoso.

Resultado spring web

Para la clase CuadradoController, utilizaremos Mockito, dado que CuadradoController posee una dependencia con CuadradoService, la cual debemos mockear o aislar a través de un doble de prueba como Mock.


En el siguiente código, mock se está instanciando de forma estática, en el método setUp() de la anotación @BeforeEach; además se está haciendo uso del método when de Mockito para cambiar el comportamiento del objeto simulado.


package com.springboot.spring.test;

import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.when;
import static org.mockito.Mockito.mock;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;

class CuadradoControllerTest {
    private CuadradoController cuadradoController;
    private CuadradoService cuadradoService;
    
    @BeforeEach
    public void setUp() {
        cuadradoService = mock(CuadradoService.class);
        cuadradoController = new CuadradoController(cuadradoService);
    }

	@Test
	void testCalcularAreaCuadrado() {
		when(cuadradoService.calcularArea(10)).thenReturn(100.0);
		ResponseEntity<Double> httpResponse = cuadradoController.calcularAreaCuadrado(10);
		
        assertEquals (HttpStatus.OK, httpResponse.getStatusCode());
        assertEquals(100, httpResponse.getBody()); 
	}

	@Test
	void testCalcularPerimetroCuadrado() {
		when(cuadradoService.calcularPerimetro(10)).thenReturn(40.0);
		ResponseEntity<Double> httpResponse = cuadradoController.calcularPerimetroCuadrado(10);
		
        assertEquals (HttpStatus.OK, httpResponse.getStatusCode());
        assertEquals(40, httpResponse.getBody()); 
	}

	@Test
	void testCalcularDiagonalCuadrado() {		
		when(cuadradoService.calcularDiagonal(10)).thenReturn(14.142135623730950488016887242097);
		ResponseEntity<Double> httpResponse = cuadradoController.calcularDiagonalCuadrado(10);
		
        assertEquals (HttpStatus.OK, httpResponse.getStatusCode());
        assertEquals(14.142135623730950488016887242097, httpResponse.getBody()); 
	}

}

Si ejecutamos nuestra prueba, veremos que también nos arroja un resultado exitoso.

Segundo resultado de spring


MockMvc con Spring

Con lo anterior, hemos abarcado, el tema de pruebas unitarias en Spring Boot, usando JUnit y Mockito, ahora, realizaremos una prueba que incluya ambos componentes o clases, haciendo uso de MockMvc; es de resaltar que si se habla estrictamente de MockMvc, este estaría entre la frontera de pruebas unitarias y pruebas de integración, por lo que no son tests unitarios porque los endpoints (permiten monitorear e interactuar con la aplicación) se prueban de forma integrada con un contenedor MVC con dependencias e inputs simulados, pero tampoco podríamos tomar estos test como de integración, ya que si se definen correctamente con MockMvc, sólo probaremos un único componente con una plataforma simulada, pero no una combinación de componentes, como normalmente sería una prueba de integración.

En el código, apreciamos unas anotaciones @ExtendWith() equivalente a @RunWith en JUnit4, el cual sirve para invocar la clase en lugar del ejecutor por defecto, la anotación @SpringBootTest, en la cual especificamos nuestra clase main (Application) para cargar el contexto de Spring, en el setup de la prueba se puede ver que inicializamos MockMvc para después usarlo llamando al endpoint especificado y hacer una prueba sobre el código de status y el contenido del resultado.

En cuanto a WebApplicationContext, esta es una extensión del ApplicationContext simple que tiene algunas características adicionales necesarias para las aplicaciones web, se diferencia de un ApplicationContext normal en que es capaz de resolver temas, y que sabe con qué Servlet está asociado al tener un enlace al ServletContext, el WebApplicationContext está enlazado en el ServletContext, y al usar métodos estáticos en la clase RequestContextUtils, siempre puede buscar el WebApplicationContext si necesita acceder a él.

Con MockMvc somos capaces que el código de respuesta se corresponde con el esperado (200, 201…), incluso cuando el código se asigna fuera del método del controlador o post-procesado por una anotación/aspect.

En este caso, haremos uso de un solo método testCalcularAreaEndPoint(), con el cual analizaremos los códigos de respuesta (response codes) y las cabeceras, mandaremos por el método HTTP POST a la URL (/área) el parámetro (lado, 5) y esperamos que el status de la respuesta sea exitosa y que la respuesta que viene sea 25.0, equivalente al área de un cuadrado cuyos lados es de 5.


package com.springboot.spring.test;

import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;

@ExtendWith(SpringExtension.class)
@SpringBootTest(classes = Application.class)
class ApplicationTest {
	@Autowired
	private WebApplicationContext wac;
	private MockMvc mockMvc;
	
	@BeforeEach
	public void setUp() {
		this.mockMvc= MockMvcBuilders.webAppContextSetup(wac).build();
		
	}
	
	@Test
	void testCalcularAreaEndPoint() throws Exception {
		this.mockMvc.perform(post("/area").param("lado","5"))
		.andExpect(status().isOk())
		.andExpect(content().string("25.0"));	
	}
}

Finalmente, si lo ejecutamos, tendremos como respuesta un succesfully.

Resultado de MOCKMVV


Y con esto, damos por terminado nuestro curso de pruebas unitarias. Esperamos que haya sido de utilidad y que lo aprendido pueda ser replicado en sus futuros proyectos.


Última modificación: miércoles, 16 de marzo de 2022, 16:04