【问题标题】:Mockito: Mock private field initializationMockito:模拟私有字段初始化
【发布时间】:2016-03-23 09:14:16
【问题描述】:

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

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

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

编辑:我不能修改 Person 类。

【问题讨论】:

  • 这个链接可能对你有帮助stackoverflow.com/questions/13645571/…
  • 你应该重构你的代码,以便你可以为Person 传递一个模拟。选项包括添加一个构造函数来执行此操作,或添加一个 setter 方法。

标签: java mockito junit4 powermockito


【解决方案1】:

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 中使用 remove the class。所以你又要重新编写自己的反射样板代码,使用另一个库(例如 Apache Commons Lang),或者干脆盗用 Whitebox 类(它是 MIT licensed) .

更新 2: JUnit 5 带有自己的 ReflectionSupport 和 AnnotationSupport 类,它们可能很有用,可以让您免于引入另一个库。

【讨论】:

  • 由于其他原因,我正在监视我的目标对象,在这种情况下,当我的对象是间谍时,我无法以这种方式设置内部状态。
  • 为什么不呢?我将它与间谍一起使用。创建一个 Person 实例。存根需要存根的任何内容,然后将其设置在您的测试实例上。
  • 警告:Whitebox 位于 internal 包中,似乎不再适用于 Mockito 2.6.2。
  • 您可以使用 FieldSetter.setField()。我在下面给出了一个相同的例子。
  • @Ralf 因为在 Java 中更改引用名称总是会导致编译错误。
【解决方案2】:

参加聚会很晚了,但我被击中并得到了朋友的帮助。问题是不要使用 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();
}

这样你可以传递一个模拟对象,然后再验证它。

Here 是参考。

【讨论】:

  • FieldSetter 在 Mockito 2.x 中不再可用。
  • @Ralf 我使用的是 mockito-core-2.15.0 版本。它在那里可用。 static.javadoc.io/org.mockito/mockito-core/2.0.15-beta/org/…。不过仍然是测试版。
  • @RajKumar Class#getDeclaredField 接受单个参数,因此括号需要看起来像这样FieldSetter.setField(underTest, underTest.getClass().getDeclaredField("person"), mockedPerson);。这就是我弄错的原因,并且认为在您的示例中修复它可能是一个好主意。感谢您的回复。
  • 使用内部 API 不是最好的主意
  • @RajKumar 不错,但正如 Zimbo Rodger 所说:使用内部 API 是个好主意吗?为什么它是内部的?它对测试非常有用。
【解决方案3】:

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

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

【讨论】:

  • 太棒了!可能有助于链接到这篇文章,并可能包括其中需要的依赖项:baeldung.com/spring-reflection-test-utils
  • build.gradle.kts: testImplementation("org.springframework:spring-test:5.1.2.RELEASE")
  • 这对 JUnit 5 或 spring 没有用:2.4.2 版本
  • @SolankiVaibhav 为什么不呢?
【解决方案4】:

我已经找到了解决这个问题的方法,但我忘了在这里发布。

@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);

【讨论】:

  • 您的解释谈到了 mockStatic 函数,但这在您的代码示例中没有表示。代码示例应该有mockStatic 调用,还是构造函数不需要调用?
【解决方案5】:

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

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

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

【讨论】:

    【解决方案6】:

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

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

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

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

    【讨论】:

      【解决方案7】:

      如果您正在使用 Spring Boot 测试并且找不到 WhiteBox、FeildSetter 中的任何一个;你可以简单地使用org.springframework.test.util.ReflectionTestUtils

      这是一个例子:

      import org.springframework.test.util.ReflectionTestUtils;
      
      //...
      
      @Mock
      private Person mockedPerson;
      private Test underTest;
      
      // ...
      
      @Test
      public void testMethod() {
          ReflectionTestUtils.setField(underTestObject, "person", mockedPerson);
          // ...
      }
      

      【讨论】:

        【解决方案8】:

        到目前为止,我认为最好的方法是

        org.springframework.test.util.ReflectionTestUtils

        我即将在 FooServiceTest.java 中的 FooService.class 中模拟 private String mockField

        FooService.java:

          @Value("${url.image.latest}")
          private String latestImageUrl;
        

        FooServiceTest.java:

          @InjectMocks
          FooService service;
        
          @BeforeEach
          void setUp() {
            ReflectionTestUtils.setField(service, // inject into this object
                "latestImageUrl", // assign to this field
                "your value here"); // object to be injected
          }
        

        【讨论】:

          猜你喜欢
          • 2013-03-07
          • 2017-12-04
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2015-08-22
          • 2012-06-28
          • 2015-12-13
          • 2013-06-20
          相关资源
          最近更新 更多