Comprendiendo las diferencias entre @Spy y @SpyBean en JAVA
1. Introduction
En este tutorial, abordaremos la diferencia entre @Spy
y @SpyBean
, explicando sus funcionalidades y proporcionando orientación sobre cuándo emplear cada uno. Estos dos tipos de anotaciones son útiles en el marco de las pruebas para gestionar cómo se comportan los objetos en nuestras aplicaciones, lo que resulta esencial para garantizar un código robusto y mantener la calidad del software a medida que crecemos y iteramos en nuestros proyectos.
2. Basic Application
Para este artículo, utilizaremos una aplicación de pedidos simple que incluye un servicio de pedidos para crear pedidos y que llama a un servicio de notificación para avisar cuando se procesa un pedido.
La clase OrderService
tiene un método save()
que recibe un objeto Order
, lo guarda utilizando OrderRepository
, y luego invoca NotificationService
:
@Service
public class OrderService {
public final OrderRepository orderRepository;
public final NotificationService notificationService;
public OrderService(OrderRepository orderRepository, NotificationService notificationService) {
this.orderRepository = orderRepository;
this.notificationService = notificationService;
}
public Order save(Order order) {
order = orderRepository.save(order);
notificationService.notify(order);
if(!notificationService.raiseAlert(order)){
throw new RuntimeException("Alert not raised");
}
return order;
}
}
Para simplificar, asumamos que el método notify()
solo registra el pedido. En la realidad, podría implicar acciones más complejas, como enviar correos electrónicos o mensajes a aplicaciones descendentes a través de una cola.
Supongamos también que cada pedido creado debe recibir una alerta llamando a un ExternalAlertService
, que devuelve verdadero si la alerta es exitosa. El OrderService
fallará si no se lanza la alerta:
@Component
public class NotificationService {
private ExternalAlertService externalAlertService;
public void notify(Order order){
System.out.println(order);
}
public boolean raiseAlert(Order order){
return externalAlertService.alert(order);
}
}
El método save()
en OrderRepository
guarda el objeto order
en memoria usando un HashMap
:
public Order save(Order order) {
UUID orderId = UUID.randomUUID();
order.setId(orderId);
orders.put(orderId, order);
return order;
}
@Spy y @SpyBean Annotations in Action
Ahora que tenemos una aplicación básica en marcha, veamos cómo probar diferentes aspectos de ella con las anotaciones @Spy
y @SpyBean
.
3.1. Mockito’s @Spy Annotation
La anotación @Spy
, que forma parte del marco de pruebas Mockito, crea un spía (partial mock) de un objeto real y se utiliza comúnmente para pruebas unitarias.
Un spía permite rastrear y, opcionalmente, simular o verificar métodos específicos de un objeto real mientras se ejecuta la implementación real para otros métodos.
A continuación, escribamos una prueba unitaria para el OrderService
y anotemos el NotificationService
con @Spy
:
@Spy
OrderRepository orderRepository;
@Spy
NotificationService notificationService;
@InjectMocks
OrderService orderService;
@Test
void givenNotificationServiceIsUsingSpy_whenOrderServiceIsCalled_thenNotificationServiceSpyShouldBeInvoked() {
UUID orderId = UUID.randomUUID();
Order orderInput = new Order(orderId, "Test", 1.0, "17 St Andrews Croft, Leeds ,LS17 7TP");
doReturn(orderInput).when(orderRepository).save(any());
doReturn(true).when(notificationService).raiseAlert(any(Order.class));
Order order = orderService.save(orderInput);
Assertions.assertNotNull(order);
Assertions.assertEquals(orderId, order.getId());
verify(notificationService).notify(any(Order.class));
}
En este caso, el NotificationService
actúa como un objeto espía e invoca el verdadero método notify()
cuando no se define un simulacro. Además, dado que definimos un simulacro para el método raiseAlert()
, el NotificationService
se comporta como un simulacro parcial.
3.2. Spring Boot’s @SpyBean Annotation
La anotación @SpyBean
, por otro lado, es específica de Spring Boot y se utiliza para pruebas de integración con la inyección de dependencias de Spring. Permite crear un espía (partial mock) de un bean de Spring mientras se sigue utilizando la definición real del bean desde nuestro contexto de aplicación.
Veamos cómo podemos agregar una prueba de integración utilizando @SpyBean
para el NotificationService
:
@Autowired
OrderRepository orderRepository;
@SpyBean
NotificationService notificationService;
@SpyBean
OrderService orderService;
@Test
void givenNotificationServiceIsUsingSpyBean_whenOrderServiceIsCalled_thenNotificationServiceSpyBeanShouldBeInvoked() {
Order orderInput = new Order(null, "Test", 1.0, "17 St Andrews Croft, Leeds ,LS17 7TP");
doReturn(true).when(notificationService).raiseAlert(any(Order.class));
Order order = orderService.save(orderInput);
Assertions.assertNotNull(order);
Assertions.assertNotNull(order.getId());
verify(notificationService).notify(any(Order.class));
}
En este caso, el contexto de la aplicación de Spring gestiona el NotificationService
y lo inyecta en el OrderService
. Invocar notify()
dentro de NotificationService
activa la ejecución del método real, y invocar raiseAlert()
activa la ejecución del mock.
4. Differences Between @Spy and @SpyBean
Es importante entender la diferencia entre @Spy
y @SpyBean
.
En las pruebas unitarias, utilizamos @Spy
, mientras que en las pruebas de integración, usamos @SpyBean
.
Si el componente anotado con @Spy
contiene otras dependencias, podemos declararlas durante la inicialización. Si no se proporcionan durante la inicialización, el sistema usará un constructor sin argumentos si está disponible. En el caso de la prueba @SpyBean
, debemos usar la anotación @Autowired
para inyectar el componente dependiente. De lo contrario, durante la ejecución, Spring Boot creará una nueva instancia.
Si utilizamos @SpyBean
en el ejemplo de prueba unitaria, la prueba fallará con un NullPointerException
cuando se invoque NotificationService
, ya que OrderService
espera un mock/spy de NotificationService
. De forma similar, si se usa @Spy
en el ejemplo del test de integración, la prueba fallará con el mensaje de error ‘Wanted but not invoked: notificationService.notify(<any com.baeldung.spytest.Order>)’, porque el contexto de aplicación de Spring no está al tanto del método anotado con @Spy
y, en su lugar, crea una nueva instancia de NotificationService
.
5. Conclusion
En este artículo, exploramos las anotaciones @Spy
y @SpyBean
y cuándo usarlas. La clave radica en entender el contexto de prueba en el que se utilizan. En los tests unitarios, @Spy
es ideal para simular comportamientos y verificar la lógica de negocios, mientras que @SpyBean
es esencial para pruebas de integración cuando trabajamos con el ecosistema de Spring.
Algunos consejos prácticos para programadores especializados en JAVA al utilizar estas anotaciones incluyen:
- Identificar el contexto de prueba: Asegúrese de usar
@Spy
para pruebas unitarias y@SpyBean
para pruebas de integración. - Verificaciones claras: Utilice
verify()
para asegurar que se invocan los métodos esperados en sus pruebas. - Modificar el comportamiento: En pruebas unitarias, puede fácilmente cambiar el comportamiento de un método utilizando
doReturn()
para simular resultados específicos.
Dominar el uso de estas herramientas puede mejorar significativamente sus capacidades de prueba y resultar en una base de código más sólida y mantenible. ¡Feliz codificación y pruebas!