用 Mockito 模擬私有字段
一、概述
在本教程中,我們將學習如何使用 Mockito 模擬私有字段。 Mockito 是一種流行的模擬框架,通常與 JUnit 一起使用以在 Java 中創建模擬對象。它本身並不支持模擬私有字段。
但是,我們可以使用不同的方法通過 Mockito 模擬私有字段。讓我們來看看其中的幾個。
2.項目設置
讓我們從創建我們將在示例中使用的類開始。我們將創建一個帶有私有字段的類和一個測試類來測試它。
2.1.源類
首先,我們將創建一個帶有私有字段的簡單類:
`public class MockService {
private final Person person = new Person("John Doe");
public String getName() {
return person.getName();
}
}`
MockService
類有一個類型為Person
的private
字段person
。它還有一個返回人名的方法getName()
。如我們所見, person
字段不存在 setter 方法。所以,我們不能直接設置字段或者改變字段的值。
接下來,我們將創建Person
類:
`public class Person {
private final String name;
public Person(String name) {
this.name = name;
}
public String getName() {
return name;
}
}`
Person
類有一個private
字段name
和該字段的 getter 方法。
2.2.測試類
接下來,我們將創建一個測試類來測試MockService
類:
`public class MockServiceUnitTest {
private Person mockedPerson;
@BeforeEach
public void setUp(){
mockedPerson = mock(Person.class);
}
}`
我們創建一個Person
類的實例並使用 Mockito 模擬它。在接下來的部分中,我們將研究使用此模擬實例替換MockService
類的私有字段的方法。
3. 使用 Java 反射 API 啟用模擬
設置私有字段的方法之一是使用 Java Reflection API。這是一個很好的方法,因為它不需要任何額外的依賴項。我們可以首先使字段可訪問,然後將字段的值設置為模擬實例。
讓我們看一下執行此操作的代碼:
`@Test
void givenNameChangedWithReflection_whenGetName_thenReturnName() throws Exception {
Class<?> mockServiceClass = Class.forName("com.baeldung.mockprivate.MockService");
MockService mockService = (MockService) mockServiceClass.getDeclaredConstructor().newInstance();
Field field = mockServiceClass.getDeclaredField("person");
field.setAccessible(true);
field.set(mockService, mockedPerson);
when(mockedPerson.getName()).thenReturn("Jane Doe");
Assertions.assertEquals("Jane Doe", mockService.getName());
}`
我們使用Class.forName()
方法來獲取MockService
類的類對象。然後,我們使用getDeclaredConstructor()
方法創建MockService
類的實例。
接下來,我們使用getDeclaredField()
方法獲取MockService
類的person
字段。我們使用setAccessible()
方法使字段可訪問,並使用set()
方法將字段的值設置為模擬實例。
最後,我們可以模擬Person
類的getName()
方法並測試MockService
類的getName()
方法返回模擬值。
4. 使用 JUnit 5 啟用模擬
與 Java Reflection API 類似,JUnit 5 也提供實用方法來設置私有字段。我們可以使用 JUnit 5 的ReflectionUtils
類為私有字段設置一個值:
`@Test
void givenNameChangedWithReflectionUtils_whenGetName_thenReturnName() throws Exception {
MockService mockService = new MockService();
Field field = ReflectionUtils
.findFields(MockService.class, f -> f.getName().equals("person"),
ReflectionUtils.HierarchyTraversalMode.TOP_DOWN)
.get(0);
field.setAccessible(true);
field.set(mockService, mockedPerson);
when(mockedPerson.getName()).thenReturn("Jane Doe");
Assertions.assertEquals("Jane Doe", mockService.getName());
}`
此方法的工作方式與前面的方法相同。主要區別在於我們如何獲得該字段:
- 我們使用
ReflectionUtils.findFields()
方法來獲取字段。 - 它採用
MockService
類的類對象和一個謂詞來查找該字段。我們在這裡使用的謂詞是找到名稱為“person”
的字段。 - 此外,我們需要指定
HierarchyTraversalMode
。當我們有類的層次結構並且我們想在層次結構中找到字段時,這很重要。 - 在我們的例子中,我們只有一個類,因此我們可以使用任何
TOP_DOWN
或BOTTOM_UP
模式。
這為我們提供了字段,我們可以再次設置字段的值並執行測試。
5. 使用 Spring 測試啟用模擬
如果我們的項目使用 Spring,Spring Test 提供了一個實用程序類ReflectionTestUtils
來設置私有字段。
5.1.依賴性
讓我們首先將Spring Test依賴項添加到我們的項目中:
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>5.3.25</version> <scope>test</scope> </dependency>
或者,如果使用 Spring Boot,我們可以使用Spring Boot Starter Test依賴項來執行相同的操作。
5.2.測試用例
接下來,讓我們在測試中使用此類來啟用模擬:
`@Test
void givenNameChangedWithReflectionTestUtils_whenGetName_thenReturnName() throws Exception {
MockService mockService = new MockService();
ReflectionTestUtils.setField(mockService, "person", mockedPerson);
when(mockedPerson.getName()).thenReturn("Jane Doe");
Assertions.assertEquals("Jane Doe", mockService.getName());
}`
在這裡,我們使用ReflectionTestUtils.setField()
方法來設置私有字段。在內部,這也使用 Java Reflection API 來設置字段,但不需要樣板代碼。
六,結論
在本文中,我們研究了使用 Mockito 模擬私有字段的不同方法。我們探索了 Java Reflection API、JUnit 5 和 Spring Test 來模擬私有字段。
與往常一樣,這些示例的代碼在 GitHub 上可用。