PowerMockito returns 模拟用于测试 public 方法的私有方法时为 null

PowerMockito returns null when mocking private method for testing public method

我正在尝试编写一个测试来比较字符串的相等性。

这是应该测试的 class 的代码片段

package ge.jibo.util;

public class TextManager {

  private String concatenateTwoString(String t1, String t2) {
    return t1 + t2;
  }

  public String concatenate(String t1, String t2) {
    return concatenateTwoString(t1, t2);
  }

}

这是一个测试class

package ge.jibo.util;

import org.junit.jupiter.api.Test;
import org.mockito.Mockito;
import org.powermock.api.mockito.PowerMockito;
import org.powermock.core.classloader.annotations.PrepareForTest;

import static org.assertj.core.api.Assertions.assertThat;

public class TextManagerTest {

  @Test
  @PrepareForTest(TextManager.class)
  void checkIfConcatenationWorks() throws Exception {
    TextManager textmanager = PowerMockito.spy(new TextManager());
    PowerMockito.doReturn("someText").when(textmanager,"concatenateTwoString", Mockito.anyString(), Mockito.anyString());
    String text = textmanager.concatenate("ji","bo");
    assertThat(text).isEqualTo("jibo");
  }
}

如你所见,我想测试public方法concatenate,它在同一个class中调用私有方法concatenateTwoString。 我的想法是,我想为私有方法制作一个模拟对象,每当从 public 方法调用它时,它应该 return 常量值 "someText"

但它 return 是私有方法 concatenateTwoString 中的 null 而不是 "someText"

org.opentest4j.AssertionFailedError: 
Expecting:
 <null>
to be equal to:
 <"jibo">
but was not.
Expected :jibo
Actual   :null

有人知道怎么解决吗?

依赖版本:

  1. junit-木星 - 5.7.2
  2. junit 平台启动器 - 1.8.1
  3. 模拟核心 - 3.12.4
  4. mockito-junit-jupiter - 3.12.4
  5. powermock-核心 - 2.0.9
  6. powermock-api-mockito2 - 2.0.9

您忘记添加 @RunWith 并指定 PowerMockRunner

如果您能够使用 Junit-4,这就是解决方案。

@RunWith(PowerMockRunner.class)
@PrepareForTest(TextManager.class)
public class TextManagerTest {

  @Test
  public void checkIfConcatenationWorks() throws Exception {
    // Arrange
    TextManager textmanager = PowerMockito.spy(new TextManager());
    PowerMockito.doReturn("someText").when(textmanager,"concatenateTwoString"
    , Mockito.anyString()
    , Mockito.anyString());

    // Act
    String text = textmanager.concatenate("ji","bo");

    // Assert
    assertThat(text).isEqualTo("jibo");
  }
}

正如@heaprc在他的post中提到的,“测试”方法可以正确执行,在我们使用JUnit4而不是JUnit5的情况下,通过为 powerMock @RunWith(PowerMockRunner.class).

添加 JUnit4 注释

一切正确,在JUnit5.

的情况下,没有直接模拟public下的私有方法的解决方案

那么我们有什么选择呢?

在m.o中,这种情况下有三个选项:

  1. JUnit5 留给 "Test framework",不要尝试模拟私有方法。首先,想想你是否真的想测试那个 private 方法,如果有必要测试它 package-private (通过将方法更改为 package-private 你正在打破封装的概念,但如果你真的需要测试它,那么我认为这不是一个大问题)
  2. 将您的 "Test framework"JUnit5 降级到 JUnit4。但在这种情况下,如果您已经在 JUnit5 中编写了 100 个测试,则必须更改所有测试才能在 JUnit4 中工作。必须降级框架的事实真的很烦人,而必须为 JUnit4 重写 100 个测试的事实非常烦人。
  3. JUnit4JUnit5 一起工作。我认为这是更好的解决方案,示例如下:

您的 pom.xml 文件中必须包含以下依赖项。

pom.xml

 <dependency>
        <groupId>org.junit.jupiter</groupId>
        <artifactId>junit-jupiter-engine</artifactId>
        <version>5.8.1</version>
    </dependency>
    <dependency>
        <groupId>org.junit.vintage</groupId>
        <artifactId>junit-vintage-engine</artifactId>
        <version>5.8.1</version>
    </dependency>


    <dependency>
        <groupId>org.powermock</groupId>
        <artifactId>powermock-module-junit4</artifactId>
        <version>2.0.9</version>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>org.powermock</groupId>
        <artifactId>powermock-api-mockito2</artifactId>
        <version>2.0.9</version>
        <scope>test</scope>
    </dependency>
  • junit-vintage-engine 允许您 运行 JUnit4 测试。
  • junit-jupiter-engine 允许您 运行 JUnit5 测试。

代码片段

    import org.junit.Assert;
    import org.junit.jupiter.api.Assertions;
    import org.junit.jupiter.api.Test;
    import org.junit.runner.RunWith;
    import org.mockito.Mockito;
    import org.powermock.api.mockito.PowerMockito;
    import org.powermock.core.classloader.annotations.PrepareForTest;
    import org.powermock.modules.junit4.PowerMockRunner;
    
    @PrepareForTest(TextManager.class)
    @RunWith(PowerMockRunner.class)
    public class TextManagerTest {
    
        @org.junit.Test  //Test using Junit4
        public void checkIfConcatenationWorksWithJunit4() throws Exception {
            TextManager textmanager = PowerMockito.spy(new TextManager());
            PowerMockito.doReturn("jibo").when(textmanager, "concatenateTwoString", Mockito.anyString(), Mockito.anyString());
            String text = textmanager.concatenate("ji", "bo");
            Assert.assertEquals(text,"jibo");
        }
    
        @Test  //Test using Junit5
        public void checkIfConcatenationWorksWithJunit5() {
            Assertions.assertEquals("text","jibo");
        }
    }

在这种情况下,您将 运行 使用 Junit4 平台的 checkIfConcatenationWorksWithJunit4 测试方法,并且 @RunWith(PowerMockRunner.class) 将适用于此执行。 在 checkIfConcatenationWorksWithJunit5 方法的情况下,它将 运行 与 Junit5(Jupiter) 平台。

终于

您必须在 pom.xml 文件中添加 maven-surefire-plugin 才能使 JUnit4JUnit5 测试在构建或使用 [=44= 测试时进行测试].

   <build>
        <plugins>
            <plugin>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>2.22.2</version>
            </plugin>
        </plugins>
    </build>

不添加 maven-surefire-plugin 唯一一个平台 (JUnit4) 测试将在 运行 时间执行。