Difference Between @Spy and @SpyBean

1. Introduction

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.

2. Basic Application

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:

@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.

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:

@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:

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

Now that we have a basic application in place, let’s see how to test different aspects of it with @Spy and @SpyBean annotations.

3.1. Mockito’s @Spy Annotation

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.

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:

@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

3.2. Spring Boot’s @SpyBean Annotation

On the other hand, the @SpyBean annotation is specific to Spring Boot and is used for integration testing with Spring’s dependency injection.

It allows us to create a spy (partial mock) of a Spring bean while still using the actual bean definition from our application context.

Let’s add an integration test using @SpyBean for 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.

4. Differences Between @Spy and @SpyBean

Let’s understand the difference between @Spy and @SpyBean in detail.

In unit testing, we utilize @Spy, whereas in integration testing, we employ @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.

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.

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.

5. Conclusion

In this article, we explored @Spy and @SpyBean annotations and when to use them.

As always, the source code for the examples is available over on GitHub.

       

Commercials Cooperation Advertisements:


(1) IT Teacher IT Freelance

IT電腦補習

立刻註冊及報名電腦補習課程吧!
电子计算机 -教育 -IT 電腦班” ( IT電腦補習 ) 提供一個方便的电子计算机 教育平台, 為大家配對信息技术, 電腦 老師, IT freelance 和 programming expert. 讓大家方便地就能找到合適的電腦補習, 電腦班, 家教, 私人老師.
We are a education and information platform which you can find a IT private tutorial teacher or freelance.
Also we provide different information about information technology, Computer, programming, mobile, Android, apple, game, movie, anime, animation…


(2) ITSec

https://itsec.vip/

www.ITSec.vip

www.Sraa.com.hk

www.ITSec.hk

www.Penetrationtest.hk

www.ITSeceu.uk

Secure Your Computers from Cyber Threats and mitigate risks with professional services to defend Hackers.

ITSec provide IT Security and Compliance Services, including IT Compliance Services, Risk Assessment, IT Audit, Security Assessment and Audit, ISO 27001 Consulting and Certification, GDPR Compliance Services, Privacy Impact Assessment (PIA), Penetration test, Ethical Hacking, Vulnerabilities scan, IT Consulting, Data Privacy Consulting, Data Protection Services, Information Security Consulting, Cyber Security Consulting, Network Security Audit, Security Awareness Training.

Contact us right away.

Email (Prefer using email to contact us):
SalesExecutive@ITSec.vip

Leave a Reply

Your email address will not be published. Required fields are marked *