Usa Agentic AI para generar pruebas de API más inteligentes. En minutos. Descubra cómo >>
Por qué debería comenzar a usar Mocking para las pruebas unitarias de Spring Boot ahora mismo
La infraestructura de prueba que ofrecen Spring Framework y Spring Boot facilita la escritura de pruebas JUnit para sus aplicaciones Spring. Lea esta publicación curada por expertos para obtener más información.
La infraestructura de prueba que ofrecen Spring Framework y Spring Boot facilita la escritura de pruebas JUnit para sus aplicaciones Spring. Lea esta publicación curada por expertos para obtener más información.
El Marco de primavera, junto con Spring Boot, proporciona un marco de prueba útil para escribir pruebas JUnit para sus aplicaciones Spring. En esta publicación, abordaré uno de los mayores desafíos de probar cualquier aplicación compleja: la gestión de dependencias.
Burlarse es un técnica utilizada en las pruebas unitarias para simular el comportamiento de objetos reales cuando la unidad que se prueba tiene dependencias externas. Las simulaciones, o simulacros, se utilizan en lugar de los objetos reales. El objetivo de burlarse es aislar y concentrarse en el código que se está probando y no en el comportamiento o estado de las dependencias externas.
Seamos honestos. Las aplicaciones complejas no se crean desde cero: utilizan bibliotecas, API y proyectos o servicios centrales que otra persona crea y mantiene. Como desarrolladores de Spring, aprovechamos la funcionalidad existente tanto como sea posible para que podamos dedicar nuestro tiempo y esfuerzo a lo que nos importa: la lógica comercial de nuestra aplicación. Dejamos los detalles a las bibliotecas, por lo que nuestras aplicaciones tienen muchas dependencias, que se muestran en naranja a continuación:
Un servicio Spring con múltiples dependencias
Entonces, ¿cómo enfoco las pruebas unitarias en mi aplicación (controlador y servicio) si la mayor parte de su funcionalidad depende del comportamiento de estas dependencias? ¿No estoy, al final, siempre realizando pruebas de integración en lugar de pruebas unitarias? ¿Qué pasa si necesito un mejor control sobre el comportamiento de esas dependencias o si las dependencias no están disponibles durante las pruebas unitarias?
Lo que necesito es una manera de aislar mi aplicación De esas dependencias, puedo centrar mis pruebas unitarias en el código de mi aplicación. En algunos casos, podríamos crear versiones de prueba especializadas de estas dependencias. Sin embargo, usar una biblioteca estandarizada como Mockito ofrece ventajas sobre este enfoque por varias razones:
Un servicio simulado reemplaza múltiples dependencias
En general, las aplicaciones Spring dividen la funcionalidad en Beans. Un controlador puede depender de un Service Bean y el Service Bean puede depender de un EntityManager, una conexión JDBC u otro Bean. La mayoría de las veces, las dependencias de las que debe aislarse el código bajo prueba son beans. En una prueba de integración, tiene sentido que todas las capas sean reales. Pero para las pruebas unitarias, debemos decidir qué dependencias deben ser reales y cuáles deben ser simuladas.
Spring permite a los desarrolladores definir y configurar beans utilizando XML, Java o una combinación de ambos para proporcionar una mezcla de beans simulados y reales en su configuración. Dado que los objetos simulados deben definirse en Java, se debe utilizar una clase de configuración para definir y configurar los beans simulados.
Una herramienta de prueba automatizada, como Unit Test Assistant (UTA) de Parasoft Jtest, puede ayudarlo a crear pruebas unitarias significativas que prueben la funcionalidad de sus aplicaciones Spring. Cuando UTA genera una prueba Spring, todas las dependencias de su controlador se configuran como simulacros para que cada prueba obtenga el control de la dependencia. Cuando se ejecuta la prueba, UTA detecta las llamadas a métodos realizadas en un objeto simulado para métodos que aún no tienen configurado el método simulado y recomienda que esos métodos se simulen. Luego podemos usar una solución rápida para simular automáticamente cada método.
Aquí hay un controlador de ejemplo que depende de un PersonaServicio:
@Controller
@RequestMapping("/people")
public class PeopleController {@Autowired
protected PersonService personService;@GetMapping
public ModelAndView people(Model model){
for (Person person : personService.getAllPeople()) {
model.addAttribute(person.getName(), person.getAge());
}
return new ModelAndView("people.jsp", model.asMap());
}
}
Y una prueba de ejemplo, generada por el asistente de pruebas unitarias de Parasoft Jtest:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
public class PeopleControllerTest {@Autowired
PersonService personService;MockMvc mockMvc;
// Other fields and setup
@Configuration
static class Config {// Other beans
@Bean
public PersonService getPersonService() {
return mock(PersonService.class);
}
}
@Test
public void testPeople() throws Exception {
// When
ResultActions actions = mockMvc.perform(get("/people"));
}
}
Aquí, la prueba usa una clase interna anotada con @Configuración, que proporciona dependencias de beans para el controlador bajo prueba utilizando la configuración de Java. Esto nos permite burlarnos del PersonaServicio en el método del frijol. Todavía no hay métodos simulados, por lo que cuando ejecuto la prueba, veo la siguiente recomendación:
Esto significa que el getAllPeople () se invocó el método en mi burla PersonaServicioPero la prueba aún no configura la simulación para este método. Al seleccionar la opción de solución rápida "Simulacro", la prueba se actualiza:
@Test
public void testPeople() throws Exception {
Collection<Person> getAllPeopleResult = new ArrayList<Person>();
doReturn(getAllPeopleResult).when(personService).getAllPeople();
// When
ResultActions actions = mockMvc.perform(get("/people"));
Cuando vuelvo a ejecutar la prueba, pasa. Todavía debería poblar el Colecciones que es devuelto por getAllPeople (), pero el desafío de configurar mis dependencias simuladas está resuelto.
Tenga en cuenta que podría mover el método generado de burla del método de prueba al método de frijol de la clase de configuración. Si hago esto, significa que cada prueba en la clase simulará el mismo método de la misma manera. Dejar el método burlándose en el método de prueba significa que el método puede ser burlado de manera diferente entre diferentes pruebas.
Spring Boot hace que burlarse de los frijoles sea aún más fácil. En lugar de usar un @autocableado campo para el bean en la prueba y una clase de configuración que lo define, simplemente puede usar un campo para el bean y anotarlo con @MockBean. Spring Boot creará una simulación para el bean utilizando el marco de simulación que encuentra en el classpath y lo inyectará de la misma manera que se puede inyectar cualquier otro bean en el contenedor.
Al generar pruebas de Spring Boot con Unit Test Assistant, el @MockBean se utiliza la funcionalidad en lugar de la clase de configuración.
@SpringBootTest
@AutoConfigureMockMvc
public class PeopleControllerTest {
// Other fields and setup – no Configuration class needed!@MockBean
PersonService personService;
@Test
public void testPeople() throws Exception {
…
}
}
En el primer ejemplo anterior, la clase Configuration proporcionó todos los beans al contenedor Spring. Como alternativa, puede utilizar la configuración XML para la prueba en lugar de la clase Configuración; o puedes combinar los dos. Por ejemplo:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration({ "classpath:/**/testContext.xml" })
public class PeopleControllerTest {@Autowired
PersonService personService;// Other fields and setup
@Configuration
static class Config {
@Bean
@Primary
public PersonService getPersonService() {
return mock(PersonService.class);
}
}
// Tests
}
Aquí, la clase hace referencia a un archivo de configuración XML en el @ContextConfiguración anotación (no se muestra aquí) para proporcionar la mayoría de los beans, que podrían ser beans reales o beans específicos de la prueba. También proporcionamos un @Configuración clase, donde PersonaServicio se burla. los @Primario anotación indica que incluso si una PersonaServicio bean se encuentra en la configuración XML, esta prueba utilizará el bean simulado de la @Configuración clase en su lugar. Este tipo de configuración puede hacer que el código de prueba sea más pequeño y más fácil de administrar.
Puede configurar UTA para generar pruebas utilizando cualquier @ContextConfiguración atributos que necesita.
A veces, se accede a las dependencias de forma estática. Por ejemplo, una aplicación podría acceder a un servicio de terceros a través de una llamada de método estático:
public class ExternalPersonService {
public static Person getPerson(int id) {
RestTemplate restTemplate = new RestTemplate();
try {
return restTemplate.getForObject("http://domain.com/people/" + id, Person.class);
} catch (RestClientException e) {
return null;
}
}
}
En nuestro controlador:
@GetMapping
public ResponseEntity&lt;Person&gt; getPerson(@PathVariable("id") int id, Model model)
{
Person person = ExternalPersonService.getPerson(id);
if (person != null) {
return new ResponseEntity&lt;Person&gt;(person, HttpStatus.OK);
}
return new ResponseEntity&lt;&gt;(HttpStatus.NOT_FOUND);
}
En este ejemplo, nuestro método controlador utiliza una llamada de método estático para obtener un objeto Person de un servicio de terceros. Cuando creamos una prueba JUnit para este método de controlador, se realizará una llamada HTTP real al servicio cada vez que se ejecute la prueba.
En cambio, burlémonos de la estática ExternalPersonService.getPerson () método. Esto evita la llamada HTTP y nos permite proporcionar una Persona respuesta de objeto que se adapte a nuestras necesidades de prueba. Unit Test Assistant puede facilitar la simulación de métodos estáticos con Mockito.
UTA genera una prueba para el método del controlador anterior que se ve así:
@Test
public void testGetPerson() throws Throwable {
// When
Int id = 1L;
ResultActions actions = mockMvc.perform(get("/people/" + id));
// Then
actions.andExpect(status().isOk());
}
Cuando ejecutamos la prueba, veremos que se realiza la llamada HTTP en el árbol de flujo de UTA. Busquemos la llamada a ExternalPersonService.getPerson () y burlarse de él en su lugar:
La prueba se actualiza para simular el método estático para esta prueba usando Mockito:
@Test
public void testGetPerson() throws Throwable {
MockedStatic<ExternalPersonService>mocked = mockStatic(ExternalPersonService.class);
mocks.add(mocked);Person getPersonResult = null; // UTA: default value
mocked.when(()->ExternalPersonService.getPerson(anyInt())).thenReturn(getPersonResult);// When
int id = 1;
ResultActions actions = mockMvc.perform(get("/people/" + id));// Then
actions.andExpect(status().isOk());}
Set<AutoCloseable> mocks = new HashSet&lt;&gt;();
@After
public void closeMocks() throws Throwable {
for (AutoCloseable mocked : mocks) {
mocked.close();
}
}
El Mockito MockStatic El método crea un simulacro estático para la clase, a través del cual se pueden configurar llamadas estáticas específicas. Para asegurarse de que esta prueba no afecte a otras en la misma ejecución, los objetos MockedStatic deben cerrarse cuando finaliza la prueba, por lo que todos los simulacros se cierran en el cerrarMocks() método que se agrega a la clase de prueba.
Usando UTA, ahora podemos seleccionar el obtenerResultadoPersona variable e instanciarla, de modo que la llamada al método simulado no regrese nulo:
String name="";//UTA:default value
int age=0;//UTA:default value
Person getPersonResult=newPerson(name, age);
Cuando volvamos a ejecutar la prueba, obtenerResultadoPersona se devuelve de la burlaExternalPersonService.getPerson () método y la prueba pasa.
Nota: Desde el árbol de flujo, también puede seleccionar "Añadir patrón de método simulable" para las llamadas a métodos estáticos. Esto configura el Asistente de Pruebas Unitarias para que siempre simule esas llamadas a métodos estáticos al generar nuevas pruebas.
Las aplicaciones complejas a menudo tienen dependencias funcionales que complican y limitan la capacidad de un desarrollador para realizar pruebas unitarias de su código. El uso de un marco de simulación como Mockito puede ayudar a los desarrolladores a aislar el código que se está probando de estas dependencias, lo que les permite escribir mejores pruebas unitarias con mayor rapidez. Solución de productividad para desarrolladores de Java de Parasoft facilita la gestión de dependencias configurando nuevas pruebas para usar simulacros, y encontrando simulacros de métodos faltantes en tiempo de ejecución y ayudando a los desarrolladores a generar simulacros para ellos.