【问题标题】:Replacing PowerMock's @PrepareForTest programmatically?以编程方式替换 PowerMock 的 @PrepareForTest?
【发布时间】:2013-11-07 22:27:16
【问题描述】:

我正在使用 PowerMock 来模拟 junit 测试中的静态方法,通常如下所示:

@RunWith(PowerMockRunner.class)
@PrepareForTest({Foo.class,Bar.class})
public class SomeUnitTest {

  @Before
  public void setUpTest() {
    setUpFoo();
    setUpBar();
  }

  private void setUpFoo() {
    mockStatic(Foo.class);
    when(Foo.someStaticMethod()).thenReturn(1);
  }

  private void setUpBar() {
    mockStatic(Bar.class);
    when(Bar.someStaticMethod()).thenReturn(2);
  }

  @Test
  public void someTestCase() {
    ...
  }

}

这很好用,但我发现指定 @PrepareForTest 注释会阻止我使我的测试 API 变得灵活。

我想做的事情如下:

public class MockLibraryOne {

  public static void setUpLibraryOne() {
    setUpFoo();
    setUpBar();
  }

  private static void setUpFoo() {
    mockStatic(Foo.class);
    when(Foo.someStaticMethod()).thenReturn(1);
  }

  private static void setUpBar() {
    mockStatic(Bar.class);
    when(Bar.someStaticMethod()).thenReturn(2);
  }

}

@RunWith(PowerMockRunner.class)
public class SomeUnitTest {

  @Before
  public void setUpTest() {
    MockLibraryOne.setUpLibraryOne();
  }

  @Test
  public void someTestCase() {
    ...
  }

}

这里我的单元测试对LibraryOne有依赖,但是它不知道LibraryOne依赖哪些类,所以它不知道要添加到@PrepareForTest注解中的哪些类。

我可以使 SomeUnitTest 扩展 MockLibraryOne 并将 @PrepareForTest 注释添加到 MockLibraryOne 类,但在其他单元测试中我将不仅仅依赖于 MockLibraryOne,因此继承不是通用的解决方案。

是否有某种方式以编程方式准备类以在 PowerMock 下进行测试,而不是使用 @PrepareForTest 注释?例如,如下所示:

public class MockLibraryOne {

  public static void setUpLibraryOne() {
    setUpFoo();
    setUpBar();
  }

  private static void setUpFoo() {
    prepareForTest(Foo.class);
    mockStatic(Foo.class);
    when(Foo.someStaticMethod()).thenReturn(1);
  }

  private static void setUpBar() {
    prepareForTest(Bar.class);
    mockStatic(Bar.class);
    when(Bar.someStaticMethod()).thenReturn(2);
  }

}

我想如果PowerMockRunner 处理@PrepareForTest 注释的方式有点不同会很好:对于每个指定的类,它不仅应该将该类(及其层次结构)添加到类列表中以准备模拟,但然后检查该类以查看它是否也有任何 @PrepareForTest 注释:

@RunWith(PowerMockRunner.class)
@PrepareForTest({MockLibraryOne.class})
public class SomeUnitTest {
  ...
}

@PrepareForTest({Foo.class,Bar.class})
public class MockLibraryOne {
  ...
}

}

因此,在此SomeUnitTest 上的@PrepareForTest 注释会找到MockLibraryOne,而@PrepareForTest 注释也会拖入Foo.classBar.class

所以也许编写我自己的测试运行器来替换 PowerMockRunner 可能是一个解决方案。

或者也许有更简单的解决方案,例如使用PowerMockAgent 类?

编辑:模拟策略可能是一种解决方案:https://code.google.com/p/powermock/wiki/MockPolicies

编辑:模拟策略适用于PowerMockRunner,但不适用于(似乎)PowerMockRule(由于类加载器问题,我有时需要)。

【问题讨论】:

  • 我也面临同样的问题。你找到解决办法了吗?
  • 你知道你可以为每个方法而不是每个类使用@PrepareForTest注解吗?

标签: unit-testing junit powermock


【解决方案1】:

你试图达到的目标是行不通的。

问题是powermock必须重写客户端类的代码来拦截静态调用,并且在类加载后它不能这样做。因此它只能在加载之前准备一个类进行测试。

假设您想在以下简单类中模拟 System.currentTimeMillis 调用。

class SystemClock {
    public long getTime() {
        return System.currentTimeMillis();
    }
}

Powermock 不会更改java.lang.System.currentTimeMillis 的代码,因为它不能。相反,它更改了SystemClock 的字节码,使其不再调用System.currentTimeMillis。相反,它会调用其他一些属于 powermock 的对象。

这就是 powermock 如何完全控制返回值并允许您编写这样的测试:

@RunWith(PowerMockRunner.class)
@PrepareForTest({ SystemClock.class })
public class PowerMockitoTest {

    @Test
    public void systemTimeMillis() {
        SystemClock systemClock = new SystemClock();

        PowerMockito.mockStatic(System.class);

        PowerMockito.when(System.currentTimeMillis()).thenReturn(12345L);

        long time = systemClock.getTime();
        assertEquals(12345L, time);
    }
}

您可以看到 powermock 在调试器的堆栈跟踪中重写了客户端类。在SystemClock.getTime 处设置断点并单步执行调用的方法。

如您所见,SystemClock 调用了 MockGateway

如果您查看MockGateway 调用堆栈上的变量,您可以看到原始System.currentTimeMillis 方法是如何处理的。

【讨论】:

    【解决方案2】:

    也许您正在寻找mock policy

    【讨论】:

    • 感谢您的回复。我调查了MockPolicy,但正如我上面提到的,它只适用于PowerMockRunner,不适用于PowerMockRule。由于类加载器问题,我需要使用PowerMockRule,而MockPolicy 初始化程序仅适用于PowerMockRunner 提供的MockClassLoader
    【解决方案3】:

    你能帮忙吗(取自文档)?

    您还可以使用通配符准备整个包进行测试:

    @PrepareForTest(fullyQualifiedNames="com.mypackage.*")

    所以您可以将整个库添加到您的准备中...

    【讨论】:

      【解决方案4】:

      为什么还要模拟静态方法?为什么不将这些静态方法封装在一个可以用 mockito 模拟的类中?

      class FooWraper {
         void someMethod() {
           Foo.someStaticMethod()
         }
      }
      

      然后你可以创建一个你的 FooWraper 的模拟。根本不需要使用 Powermock...

      【讨论】:

      • 我刚刚遇到了同样的问题,这就是我的动机:Lombok 生成静态 builder 方法,我想测试是否设置了构建器的所有字段(它们是可选的,所以这是为了防止在添加新参数时意外遗漏)。为此,我模拟了静态builder 方法以返回一个CGLIB 代理,然后我计算是否已调用所有set… 方法。但是,这会为每个测试添加四行额外的行:一行用于@PrepareForTest,另外三行用于PowerMockAgentTestInitializer.initialize 的静态块。这个问题将解决我的第一个问题。
      猜你喜欢
      • 2014-03-29
      • 1970-01-01
      • 2012-04-24
      • 1970-01-01
      • 2015-08-03
      • 1970-01-01
      • 1970-01-01
      • 2018-05-13
      • 2011-09-29
      相关资源
      最近更新 更多