@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 上取得。