1. Introduction
1.导言
In this tutorial, we aim to address the difference between @Spy and @SpyBean, explaining their functionalities and providing guidance on when to employ each one.
在本教程中,我们将讨论 @Spy 和 @SpyBean 之间的区别,解释它们的功能,并就何时使用它们提供指导。
2. Basic Application
2.基本应用
For this article, we’ll use a simple order application that includes an order service to create orders and that calls a notification service to notify when processing an order.
在本文中,我们将使用一个简单的订单应用程序,其中包括一个用于创建订单的订单服务,以及一个用于在处理订单时发出通知的通知服务。
OrderService has a save() method that takes in an Order object, saves it using OrderRepository, and invokes the NotificationService:
OrderService有一个save()方法,该方法接收一个Order对象,使用OrderRepository保存该对象,并调用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;
}
}
For simplicity, let’s assume that the notify() method logs the order. In reality, it can involve more complex actions, such as sending emails or messages to downstream applications via a queue.
为简单起见,我们假设 notify() 方法记录订单。实际上,它可能涉及更复杂的操作,例如通过队列向下游应用程序发送电子邮件或消息。
Let’s also assume that every order created must receive an alert by calling an ExternalAlertService, which returns true if the alert is successful, and the OrderService will fail if it doesn’t raise the alert:
我们还假设,每个创建的订单都必须通过调用 ExternalAlertService 来接收警报,如果警报成功,OrderService 将返回 true,如果没有发出警报,OrderService 将失败:
@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);
}
}
The save() method in OrderRepository saves the order object in memory using a HashMap:
OrderRepository 中的 save() 方法使用 HashMap 将 order 对象保存在内存中:
public Order save(Order order) {
UUID orderId = UUID.randomUUID();
order.setId(orderId);
orders.put(UUID.randomUUID(), order);
return order;
}
3. @Spy and @SpyBean Annotations in Action
3.@Spy和@SpyBean注解的实际应用
Now that we have a basic application in place, let’s see how to test different aspects of it with @Spy and @SpyBean annotations.
现在我们已经有了一个基本的应用程序,让我们看看如何使用 @Spy 和 @SpyBean 注解来测试它的不同方面。
3.1. Mockito’s @Spy Annotation
3.1.Mockito 的 @Spy 注解
The @Spy annotation, part of the Mockito testing framework, creates a spy (partial mock) of a real object and is commonly used for Unit Testing.
@Spy 注解是 Mockito 测试框架的一部分,可创建真实对象的间谍(部分模拟),通常用于单元测试。
A spy allows us to track and optionally stub or verify specific methods of a real object while still executing the real implementation for other methods.
间谍程序允许我们跟踪并选择性地存根或验证真实对象的特定方法,同时仍然执行其他方法的真实实现。
Let’s understand this by writing a unit test for the OrderService and annotating the NotificationService with @Spy:
让我们通过为 OrderService 编写单元测试并使用 @Spy 对 NotificationService 进行注解来理解这一点:
@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));
}
In this case, the NotificationService acts as a spy object and invokes the real notify() method when no mock is defined. Furthermore, because we define a mock for the raiseAlert() method, the NotificationService behaves as a partial mock
在这种情况下,NotificationService 将充当间谍对象,并在未定义 mock 时调用真正的 notify() 方法。此外,由于我们为raiseAlert()方法定义了一个 mock,因此NotificationService表现为部分 mock
3.2. Spring Boot’s @SpyBean Annotation
3.2.Spring Boot 的 @SpyBean 注解
On the other hand, the @SpyBean annotation is specific to Spring Boot and is used for integration testing with Spring’s dependency injection.
另一方面,@SpyBean注解是 Spring Boot 特有的,用于与 Spring 的依赖注入进行集成测试。
It allows us to create a spy (partial mock) of a Spring bean while still using the actual bean definition from our application context.
它允许我们创建 Spring Bean 的间谍(部分模拟),同时仍然使用应用程序上下文中的实际 Bean 定义。
Let’s add an integration test using @SpyBean for NotificationService:
让我们使用 @SpyBean 为 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));
}
In this case, the Spring application context manages the NotificationService and injects it into the OrderService. Invoking notify() within NotificationService triggers the execution of the real method, and invoking raiseAlert() triggers the execution of the mock.
在这种情况下,Spring 应用上下文管理 NotificationService 并将其注入到 OrderService 中。在 NotificationService 中调用 notify() 会触发真实方法的执行,而调用 raiseAlert() 则会触发 mock 的执行。
4. Differences Between @Spy and @SpyBean
4.@Spy 和 @SpyBean 之间的区别
Let’s understand the difference between @Spy and @SpyBean in detail.
让我们来详细了解 @Spy 和 @SpyBean 之间的区别。
In unit testing, we utilize @Spy, whereas in integration testing, we employ @SpyBean.
在单元测试中,我们使用 @Spy,而在集成测试中,我们使用 @SpyBean。
If the @Spy annotated component contains other dependencies, we can declare them during initialization. If they’re not provided during initialization, the system will use a zero-argument constructor if available. In the case of the @SpyBean test, we must use the @Autowired annotation to inject the dependent component. Otherwise, during runtime, Spring Boot creates a new instance.
如果 @Spy 注解组件包含其他依赖项,我们可以在初始化过程中声明它们。如果在初始化过程中没有提供,系统将使用零参数构造函数(如果有的话)。在 @SpyBean 测试中,我们必须使用 @Autowired 注解来注入依赖组件。否则,在运行期间,Spring Boot 将创建一个新实例。
If we use @SpyBean in the unit test example, the test will fail with a NullPointerException when NotificationService gets invoked because OrderService expects a mock/spy NotificationService.
如果我们在单元测试示例中使用 @SpyBean,测试将失败,出现 NullPointerException 当NotificationService被调用,因为 OrderService 希望使用模拟/间谍NotificationService 。
Likewise, if @Spy is used in the example of the integration test, the test will fail with the error message ‘Wanted but not invoked: notificationService.notify(<any com.baeldung.spytest.Order>),’ because the Spring application context is not aware of the @Spy annotated class. Instead, it creates a new instance of NotificationService and injects it into OrderService.
同样,如果在集成测试示例中使用 @Spy,测试将失败,并显示错误消息 “Wanted but not invoked.”:notificationService.notify(<any com.baeldung.spytest.Order>),’ 因为 Spring 应用程序上下文不知道 @Spy 注解类。相反,它会创建 NotificationService 的新实例,并将其注入 OrderService. 中。
5. Conclusion
5.结论
In this article, we explored @Spy and @SpyBean annotations and when to use them.
在本文中,我们探讨了 @Spy 和 @SpyBean 注释以及何时使用它们。
As always, the source code for the examples is available over on GitHub.
与往常一样,这些示例的源代码可在 GitHub 上获取。