Mockito:模拟私有字段初始化


95

我如何模拟正在内联初始化的字段变量?

class Test {
    private Person person = new Person();
    ...
    public void testMethod() {
        person.someMethod();
        ...
    }
}

在这里,我想person.someMethod()在测试Test.testMethod()需要模拟person变量初始化的方法时进行模拟。有什么线索吗?

编辑:我不允许修改Person类。


1
此链接可能对您有所帮助stackoverflow.com/questions/13645571/…–
Popeye

2
您应该重构代码,以便可以传递模拟Person。选项包括添加一个构造函数来执行此操作,或添加一个setter方法。
Tim Biegeleisen

Answers:


108

Mockito带有一个帮助器类,可以为您节省一些反射样板代码:

import org.mockito.internal.util.reflection.Whitebox;

//...

@Mock
private Person mockedPerson;
private Test underTest;

// ...

@Test
public void testMethod() {
    Whitebox.setInternalState(underTest, "person", mockedPerson);
    // ...
}

更新: 不幸的是,Mockito 团队决定删除 Mockito 2 中的类。因此,您将返回编写自己的反射样板代码,使用其他库(例如Apache Commons Lang)或简单地窃取Whitebox类(已获得MIT许可)。

更新2: JUnit 5附带了自己的ReflectionSupportAnnotationSupport类,它们可能有用并且可以避免引入另一个库。


由于其他原因,我正在监视目标对象,在这种情况下,如果我的对象是间谍,则无法以这种方式设置内部状态。
阿伦

为什么不?我正在与间谍一起使用它。创建一个Person实例。将需要存根的所有东西存根,然后在您的Test实例上进行设置。
拉尔夫

警告:白盒已在internal包装中,在Mockito 2.6.2上似乎不再起作用。
新星

您可以使用FieldSetter.setField()。我在下面给出了相同的示例。
拉吉·库玛

1
@Ralf因为在Java中更改引用名称总是会导致编译错误。
Joe Coder

69

晚会很晚,但是我被打中了,并得到了朋友的帮助。事情不是使用PowerMock。这适用于最新版本的Mockito。

Mockito附带了此功能org.mockito.internal.util.reflection.FieldSetter

它的主要作用是帮助您使用反射来修改私有字段。

这是您的用法:

@Mock
private Person mockedPerson;
private Test underTest;

// ...

@Test
public void testMethod() {
    FieldSetter.setField(underTest, underTest.getClass().getDeclaredField("person"), mockedPerson);
    // ...
    verify(mockedPerson).someMethod();
}

这样,您可以传递模拟对象,然后稍后对其进行验证。

是参考。


FieldSetter在Mockito 2.x中不再可用。
拉尔夫

4
@Ralf我正在使用版本嘲笑核心2.15.0。它在那里。static.javadoc.io/org.mockito/mockito-core/2.0.15-beta/org/…。虽然仍然是beta。
拉吉·库玛

1
@RajKumar Class#getDeclaredField接受单个参数,因此括号需要看起来像这样FieldSetter.setField(underTest, underTest.getClass().getDeclaredField("person"), mockedPerson);。这就是我弄错的方式,并且认为在您的示例中修复它可能是一个好主意。感谢回复。
大鹏李

1
使用内部API并不是最好的主意
David

@RajKumar不错,但是正如Zimbo Rodger所述:使用内部API是一个好主意吗?为什么它是内部的?它对于测试非常有用。
Willi Mentzel

32

如果您使用Spring Test,请尝试org.springframework.test.util.ReflectionTestUtils

 ReflectionTestUtils.setField(testObject, "person", mockedPerson);

1
大!可能有助于链接到本文,并且可能包括其中一项需要的依赖项:baeldung.com/spring-reflection-test-utils
Willi Mentzel

build.gradle.kts:testImplementation("org.springframework:spring-test:5.1.2.RELEASE")
Willi Mentzel

28

我已经找到了解决此问题的方法,我忘了在此发布。

@RunWith(PowerMockRunner.class)
@PrepareForTest({ Test.class })
public class SampleTest {

@Mock
Person person;

@Test
public void testPrintName() throws Exception {
    PowerMockito.whenNew(Person.class).withNoArguments().thenReturn(person);
    Test test= new Test();
    test.testMethod();
    }
}

该解决方案的关键点是:

  1. 使用PowerMockRunner运行我的测试用例: @RunWith(PowerMockRunner.class)

  2. 指示Powermock准备Test.class操作私有字段:@PrepareForTest({ Test.class })

  3. 最后模拟Person类的构造函数:

    PowerMockito.mockStatic(Person.class); PowerMockito.whenNew(Person.class).withNoArguments().thenReturn(person);


8
您的解释是关于该mockStatic功能的,但是在代码示例中未表示。代码示例应该mockStatic调用吗,还是构造函数不需要调用?
Shadoninja

10

以下代码可用于在REST客户端模拟中初始化映射器。该mapper字段是私有的,需要在单元测试设置期间进行设置。

import org.mockito.internal.util.reflection.FieldSetter;

new FieldSetter(client, Client.class.getDeclaredField("mapper")).set(new Mapper());

5

如果需要为所有测试将变量设置为相同的值,则可以使用@Jarda指南进行定义:

@Before
public void setClientMapper() throws NoSuchFieldException, SecurityException{
    FieldSetter.setField(client, client.getClass().getDeclaredField("mapper"), new Mapper());
}

但是请注意,将私有价值观设置为不同时应谨慎处理。如果它们是私有的,则出于某种原因。

例如,我使用它来更改单元测试中的睡眠等待时间。在真实的示例中,我想睡觉10秒钟,但是在单元测试中,我很满意它是否立即生效。在集成测试中,您应该测试实际价值。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.