1. 概述
在本教程中,我們將探討 Spring Boot 中使用 @Autowired 和 Mockito 中的 @InjectMocks 注入依賴項在 Spring Boot 測試中的用法。 我們將涵蓋需要使用這些註解的用例,並提供相應的示例。
2. 理解測試註解
在開始代碼示例之前,讓我們快速瞭解一些測試註解的基礎知識。
首先,最常用的 Mockito 註解 <em @Mock</em> 創建了依賴項的 Mock 實例,用於測試。它通常與 <em @InjectMocks</em> 結合使用,將用 <em @Mock</em> 標記的 Mock 實例注入到正在測試的目標對象中。
除了 Mockito 的註解之外,Spring Boot 的註解 <em @MockBean</em> 也可以幫助創建 Mocked Spring Bean。Mocked Bean 可以被上下文中的其他 Bean 使用。 此外,如果 Spring Context 自身創建的 Bean 可以被利用,而無需 Mock,我們可以使用 <em @Autowired</em> 註解來注入它們。
3. 示例設置
在我們的代碼示例中,我們將創建一個具有兩個依賴項的服務。然後,我們將探索如何使用上述註解來測試該服務。
3.1. 依賴項
首先,添加所需的依賴項。我們將包含 Spring Boot Starter Web 和 Spring Boot Starter Test 依賴項:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>3.3.2</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<version>3.3.2</version>
<scope>test</scope>
</dependency>
除了以上內容,我們還將添加 Mockito Core 依賴項,該依賴項將用於模擬我們的服務:
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>5.12.0</version>
</dependency>
3.2. DTO
接下來,我們創建一個 DTO,用於在我們的服務中使用。
public class Book {
private String id;
private String name;
private String author;
// constructor, setters/getters
}
3.3 服務
接下來,讓我們看看我們的服務。首先,讓我們定義一個負責數據庫交互的服務:
@Service
public class DatabaseService {
public Book findById(String id) {
// querying a Database and getting a book
return new Book("id","Name", "Author");
}
}
我們不會深入討論數據庫交互,因為它們與示例無關。我們使用 @Service 註解來聲明該類為 Service 類型的 Spring Bean。
接下來,讓我們介紹一個依賴上述服務的服務:
@Service
public class BookService {
private DatabaseService databaseService;
private ObjectMapper objectMapper;
BookService(DatabaseService databaseService, ObjectMapper objectMapper) {
this.databaseService = databaseService;
this.objectMapper = objectMapper;
}
String getBook(String id) throws JsonProcessingException {
Book book = databaseService.findById(id);
return objectMapper.writeValueAsString(book);
}
}
這裏有一個小型服務,它具有一個getBook() 方法。該方法利用DatabaseService 從數據庫中獲取書籍。然後它使用 Jackson 的 ObjectMapper API 將 Book 對象轉換為 JSON 字符串並返回。
因此,該服務有兩個依賴項:DatabaseService 和 ObjectMapper。
4. 測試
現在我們的服務已配置完成,讓我們看看如何使用我們先前定義的註解來測試 BookService。
4.1. 使用 @Mock 和 @InjectMocks
第一種方法是使用 @Mock 模擬服務的所有依賴項,並使用 @InjectMocks 將它們注入到服務中。下面我們創建一個測試類來演示:
@ExtendWith(MockitoExtension.class)
class BookServiceMockAndInjectMocksUnitTest {
@Mock
private DatabaseService databaseService;
@Mock
private ObjectMapper objectMapper;
@InjectMocks
private BookService bookService;
@Test
void givenBookService_whenGettingBook_thenBookIsCorrect() throws JsonProcessingException {
Book book1 = new Book("1234", "Inferno", "Dan Brown");
when(databaseService.findById(eq("1234"))).thenReturn(book1);
when(objectMapper.writeValueAsString(any())).thenReturn(new ObjectMapper().writeValueAsString(book1));
String bookString1 = bookService.getBook("1234");
Assertions.assertTrue(bookString1.contains("Dan Brown"));
}
}
首先,我們使用 @ExtendWith(MockitoExtension.class) 對測試類進行標註。 MockitoExtenstion 擴展允許我們在測試中模擬和注入對象。
接下來,我們聲明 DatabaseService 和 ObjectMapper 字段並使用 @Mock 標註它們。 這會創建這兩個對象的模擬對象。 當我們聲明我們要測試的 BookService 實例時,我們會添加 @InjectMocks 標註。 這會注入服務所需並之前使用 @Mocks 標註的所有依賴項。
最後,在我們的測試中,我們模擬模擬對象的行為,並測試服務的 getBook() 方法。
使用此方法時,必須模擬服務的所有依賴項。 例如,如果我們不模擬 ObjectMapper,則在被測試的方法中調用時會導致 NullPointerException。
4.2. 使用 @Autowired 與 @MockBean
在上述方法中,我們已經模擬了所有依賴項。但是,可能需要模擬某些依賴項,而不模擬其他依賴項。假設我們不需要模擬 ObjectMapper 的行為,而是僅模擬 DatabaseService。
由於我們在測試中加載了 Spring 上下文,因此可以使用 @Autowired 和 @MockBean 兩個註解來實現組合使用:
@SpringBootTest
class BookServiceAutowiredAndInjectMocksUnitTest {
@MockBean
private DatabaseService databaseService;
@Autowired
private BookService bookService;
@Test
void givenBookService_whenGettingBook_thenBookIsCorrect() throws JsonProcessingException {
Book book1 = new Book("1234", "Inferno", "Dan Brown");
when(databaseService.findById(eq("1234"))).thenReturn(book1);
String bookString1 = bookService.getBook("1234");
Assertions.assertTrue(bookString1.contains("Dan Brown"));
}
}首先,要使用 Spring 上下文中定義的 Bean,我們需要用 @SpringBootTest 註解我們的測試類。
接下來,我們用 @MockBean 註解 DatabaseService。然後,我們使用 @Autowired 從應用程序上下文中獲取 BookService 實例。
當 BookService Bean 被注入時,實際的 DatabaseService Bean 將被替換為模擬的 Bean。
反之,ObjectMapper Bean 保持與應用程序最初創建時的相同。
現在測試這個實例時,我們不需要為 ObjectMapper 模擬任何行為。
此方法在我們需要測試嵌套 Bean 的行為,並且不想模擬所有依賴項時非常有用。
4.3. 使用 @Autowired 和 @InjectMocks 協同使用
我們可以使用 @InjectMocks 代替 @MockBean 用於上述用例。
讓我們查看代碼以瞭解這兩種方法的區別:
@Mock
private DatabaseService databaseService;
@Autowired
@InjectMocks
private BookService bookService;
@Test
void givenBookService_whenGettingBook_thenBookIsCorrect() throws JsonProcessingException {
Book book1 = new Book("1234", "Inferno", "Dan Brown");
MockitoAnnotations.openMocks(this);
when(databaseService.findById(eq("1234"))).thenReturn(book1);
String bookString1 = bookService.getBook("1234");
Assertions.assertTrue(bookString1.contains("Dan Brown"));
}
在這裏,我們使用 DatabaseService 並使用 @Mock 代替 @MockBean 進行模擬。除了 @Autowired,我們還將 @InjectMocks 註解添加到 BookService 實例中。
當同時使用這兩個註解時,@InjectMocks 不會自動注入模擬的依賴項,並且當測試開始時,自動注入的 BookService 對象會被注入。
但是,我們可以通過在測試中使用 MockitoAnnotations.openMocks() 方法來注入模擬的 DatabaseService 實例。 此方法查找帶有 @InjectMocks 標記的字段,並將模擬對象注入到這些字段中。
我們在需要模擬 DatabaseService 的行為之前,在測試中使用它。 此方法在我們需要動態地決定何時使用模擬對象以及何時使用實際的依賴項 bean 時非常有用。
5. 方法比較
現在我們已經看過多種方法,下面我們總結一下它們之間的比較:
| 方法 | 描述 | 使用場景 |
|---|---|---|
| @Mock 與 @InjectMocks | 使用 Mockito 的 @Mock 註解創建依賴項的模擬實例,並使用 @InjectMocks 將這些模擬實例注入到被測試的目標對象中。 | 適用於單元測試,我們希望模擬測試類的所有依賴項。 |
| @MockBean 與 @Autowired | 利用 Spring Boot 的 @MockBean 註解創建模擬的 Spring Bean,並使用 @Autowired 注入這些 Bean。 | 適用於 Spring Boot 應用程序的集成測試。它允許模擬一些 Spring Bean,同時從 Spring 的依賴注入中獲取其他 Bean。 |
| @InjectMocks 與 @Autowired | 使用 Mockito 的 @Mock 註解創建模擬實例,並使用 @InjectMocks 將這些模擬實例注入到 Spring 自動裝配的 Bean 中。 | 在需要臨時使用 Mockito 覆蓋 Spring 注入的 Bean 場景中提供靈活性。在 Spring 應用程序中測試複雜場景時很有用。 |
6. 結論
在本文中,我們探討了Mockito和Spring Boot註解的不同使用場景,包括@Mock, @InjectMocks, @Autowired 和@MockBean。我們根據測試需求,探索了不同組合註解的使用時機。