使用Mockito Mock私有字段

2023/06/07

1. 概述

在本教程中,我们将学习如何使用Mockito mock私有字段。Mockito是一种流行的mock框架,通常与JUnit一起使用以在Java中创建mock对象。它本身并不支持mock私有字段。

但是,我们可以使用不同的方法通过Mockito mock私有字段,让我们来看看其中的几个。

2. 项目设置

我们将创建一个带有私有字段的类和一个测试类来测试它。

2.1 源类

首先,我们将创建一个带有私有字段的简单类:

public class MockService {
    private final Person person = new Person("John Doe");

    public String getName() {
        return person.getName();
    }
}

MockService类有一个类型为Person的私有字段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类有一个私有字段name和该字段的getter方法。

2.2 测试类

接下来,我们将创建一个测试类来测试MockService类:

public class MockServiceUnitTest {
    private Person mockedPerson;

    @BeforeEach
    public void setUp(){
        mockedPerson = mock(Person.class);
    }
}

我们创建一个Person类的实例并使用Mockito mock它。在接下来的部分中,我们将研究使用此mock实例替换MockService类的私有字段的方法。

3. 使用Java反射API启用Mock

设置私有字段的方法之一是使用Java反射API。这是一个很好的方法,因为它不需要任何额外的依赖项。我们可以首先使字段可访问,然后将字段的值设置为mock实例

让我们看一下执行此操作的代码:

@Test
void givenNameChangedWithReflection_whenGetName_thenReturnName() throws Exception {
    Class<?> mockServiceClass = Class.forName("cn.tuyucheng.taketoday.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类的Class对象。然后,我们使用getDeclaredConstructor()方法创建MockService类的实例。

接下来,我们使用getDeclaredField()方法获取MockService类的person字段。我们使用setAccessible()方法使字段可访问,并使用set()方法将字段的值设置为mock实例

最后,我们可以mock Person类的getName()方法并测试MockService类的getName()方法返回mock值。

4. 使用JUnit 5启用Mock

与Java反射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类的Class对象和一个谓词来查找该字段,我们在这里使用的谓词是查找名称为“person”的字段
  • 此外,我们需要指定HierarchyTraversalMode,当我们的类有层次结构并且我们想在层次结构中找到字段时,这很重要
  • 在我们的例子中,我们只有一个类,因此我们可以使用任何TOP_DOWN或BOTTOM_UP模式

这为我们提供了字段,我们可以再次设置字段的值并执行测试。

5. 使用Spring测试启用Mock

如果我们的项目使用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 测试用例

接下来,让我们在测试中使用此类来启用mock:

@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反射API来设置字段,但不需要样板代码。

6. 总结

在本文中,我们研究了使用Mockito mock私有字段的不同方法。我们探索了Java反射API、JUnit 5和Spring Test来mock私有字段。

与往常一样,本教程的完整源代码可在GitHub上获得。

Show Disqus Comments

Post Directory

扫码关注公众号:Taketoday
发送 290992
即可立即永久解锁本站全部文章