以编程方式替换PowerMock的@PrepareForTest?

时间:2013-10-28 23:17:10

标签: unit-testing junit powermock

我正在使用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以上的@PrepareForTest其他单元测试,所以继承不是一般的解决方案。

是否有某种方法可以在PowerMock下以编程方式准备类进行测试,而不是使用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 { ... } 注释:

@PrepareForTest

}

因此,SomeUnitTest上的MockLibraryOne注释会找到@PrepareForTest,而Foo.class注释会拖入Bar.classPowerMockRunner同样。

因此,编写我自己的测试运行器以替换PowerMockAgent可能是一种解决方案。

或许有一个更简单的解决方案,例如使用PowerMockRunner类?

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

编辑:模拟策略适用于PowerMockRule但不适用于{{1}}(我有时会因为类加载器问题而需要)。

4 个答案:

答案 0 :(得分:0)

也许您正在寻找mock policy

答案 1 :(得分:0)

为什么你甚至想模拟静态方法?为什么不将这些静态方法包装在可以用mockito模拟的类中?

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

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

答案 2 :(得分:0)

您能帮忙吗(摘自文档)?

  

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

     

@PrepareForTest(fullyQualifiedNames =“ com.mypackage。*”)

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

答案 3 :(得分:0)

您尝试实现的目标将无效。

问题在于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在调试器的stacktrace中重写了客户端类。在SystemClock.getTime处设置一个断点,然后进入所调用的方法。

Powermock stacktrace

如您所见,SystemClock会调用MockGateway

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

debug variables