@Spy 和 @SpyBean 之間的區別
一、簡介
在本教程中,我們的目標是解決@Spy和@SpyBean之間的差異,解釋它們的功能並提供有關何時使用它們的指導。
2. 基本應用
在本文中,我們將使用一個簡單的訂單應用程序,其中包含用於創建訂單的訂單服務,並在處理訂單時調用通知服務來發出通知。
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;
}
}
為簡單起見,我們假設notify()方法記錄訂單。實際上,它可能涉及更複雜的操作,例如透過佇列向下游應用程式發送電子郵件或訊息。
我們也假設建立的每個訂單都必須透過呼叫ExternalAlertService來接收警報,如果警報成功則傳回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);
}
}
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和@SpyBean註解的實際應用
現在我們已經有了一個基本的應用程序,讓我們看看如何使用@Spy和@SpyBean註釋來測試它的不同方面。
3.1. Mockito 的@Spy註釋
@Spy註解是 Mockito 測試框架的一部分,它創建真實物件的間諜(部分模擬),通常用於單元測試。
間諜允許我們追蹤並可選地存根或驗證真實物件的特定方法,同時仍執行其他方法的真實實作。
讓我們透過為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));
}
在這種情況下, NotificationService充當間諜對象,並在沒有定義mock時呼叫真正的notify()方法。此外,因為我們為raiseAlert()方法定義了一個模擬,所以NotificationService行為就像一個部分模擬
3.2. Spring Boot 的@SpyBean註解
另一方面, @SpyBean註解是Spring Boot特有的,用於與Spring的依賴注入進行整合測試。
它允許我們創建 Spring bean 的間諜(部分模擬),同時仍然使用應用程式上下文中的實際 bean 定義。
讓我們使用@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));
}
在這種情況下,Spring應用程式上下文管理NotificationService並將其註入到OrderService中。在NotificationService中呼叫notify()會觸發真正方法的執行,而呼叫raiseAlert()會觸發mock的執行。
4. @Spy和@SpyBean之間的區別
讓我們詳細了解一下@Spy和@SpyBean之間的差異。
在單元測試中,我們使用@Spy ,而在整合測試中,我們使用@SpyBean 。
如果@Spy註解的元件包含其他依賴項,我們可以在初始化時聲明它們。如果在初始化期間未提供它們,系統將使用零參數建構函數(如果可用)。在@SpyBean測試的情況下,我們必須使用@Autowired註解來注入依賴元件。否則,在執行時,Spring Boot 會建立一個新實例。
如果我們在單元測試範例中使用@SpyBean,則當OrderService NotificationService時**@SpyBean測試將失敗並出現NullPointerException ,**因為OrderService需要模擬/間諜NotificationService 。
同樣,如果在整合測試的範例中使用@Spy ,則測試將失敗並顯示錯誤訊息“Wanted but not invoked: notificationService.notify(<any com.baeldung.spytest.Order> )”,因為 Spring 應用程式context 不知道@Spy註解的類別。相反,它會建立一個新的NotificationService實例並將其註入到OrderService.
5. 結論
在本文中,我們探討了@Spy和@SpyBean註釋以及何時使用它們。
與往常一樣,範例的原始程式碼可在 GitHub 上取得。