Introduction
In this chapter, we will learn about the @SpyBean annotation in Mockito, specifically within the context of Spring Boot applications. The @SpyBean annotation is used to create and inject spy objects into the Spring application context. Spies are partial mocks that allow you to call real methods while still being able to stub and verify certain behaviors.
Key Points about the @SpyBean Annotation
- Spring Context Integration: The
@SpyBeanannotation is used to add spies to the Spring application context. - Partial Mocks: Allows real methods to be called while enabling stubbing and verification.
- Overrides Existing Beans: It can override existing beans of the same type in the application context.
- Useful for Integration Tests: Particularly useful for integration tests where you want to partially mock certain dependencies.
- Initialization: Managed by the Spring TestContext framework.
Setting Up Mockito and Spring Boot for @SpyBean Annotation
Ensure that you have the necessary dependencies in your pom.xml if you are using Maven:
<dependencies>
<!-- Mockito Core dependency -->
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>5.2.0</version>
<scope>test</scope>
</dependency>
<!-- Spring Boot Starter Test dependency -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<version>3.1.2</version>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- H2 Database dependency -->
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
</dependencies>
Example: Using @SpyBean Annotation
Description
In this example, we will create a BookService class that depends on a NotificationService class. The BookService class will use the NotificationService class to send notifications when a book is added. We will create an integration test for the BookService class using Mockito’s @SpyBean annotation to partially mock the NotificationService.
Class Under Test: BookService
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class BookService {
private final NotificationService notificationService;
@Autowired
public BookService(NotificationService notificationService) {
this.notificationService = notificationService;
}
public void addBook(Book book) {
// Logic to add the book (omitted for brevity)
notificationService.sendNotification(book);
}
}
Supporting Class: NotificationService
import org.springframework.stereotype.Service;
@Service
public class NotificationService {
public void sendNotification(Book book) {
// Logic to send notification (e.g., send an email)
}
}
Entity Class: Book
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
@Entity
public class Book {
@Id
private Long id;
private String isbn;
private String title;
private String author;
// getters and setters
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getIsbn() {
return isbn;
}
public void setIsbn(String isbn) {
this.isbn = isbn;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getAuthor() {
return author;
}
public void setAuthor(String author) {
this.author = author;
}
}
Test Class: BookServiceTest
import static org.mockito.Mockito.*;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.SpyBean;
import static org.junit.jupiter.api.Assertions.*;
@SpringBootTest
public class BookServiceTest {
@SpyBean
private NotificationService notificationService;
@Autowired
private BookService bookService;
@Test
void testAddBook_SendsNotification() {
// Arrange
Book book = new Book();
book.setIsbn("1234567890");
book.setTitle("Mockito in Action");
book.setAuthor("John Doe");
doNothing().when(notificationService).sendNotification(any(Book.class));
// Act
bookService.addBook(book);
// Assert
verify(notificationService).sendNotification(book);
}
}
Explanation
-
SpyBean Annotation:
@SpyBean private NotificationService notificationService;The
@SpyBeanannotation creates a spy object for theNotificationServiceclass and injects it into the Spring application context. This spy will override any existing bean of the same type. -
Autowired Annotation:
@Autowired private BookService bookService;The
@Autowiredannotation injects theBookServicebean from the Spring application context. TheBookServicebean will use the spiedNotificationService. -
Test Method:
@Test void testAddBook_SendsNotification() { // Arrange Book book = new Book(); book.setIsbn("1234567890"); book.setTitle("Mockito in Action"); book.setAuthor("John Doe"); doNothing().when(notificationService).sendNotification(any(Book.class)); // Act bookService.addBook(book); // Assert verify(notificationService).sendNotification(book); }This test method sets up the spy behavior (Arrange), calls the method under test (Act), and checks the expected results (Assert). It verifies that the
NotificationService‘ssendNotificationmethod was called with the correct arguments.
Conclusion
The @SpyBean annotation in Mockito is used for creating and injecting spy objects into the Spring application context. It allows you to call real methods while still enabling stubbing and verification. In this chapter, we demonstrated how to use the @SpyBean annotation with a simple BookService example. This approach helps ensure that your Spring Boot application works correctly with partially mocked dependencies, making your tests more reliable and maintainable.