31

我正在使用 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 {
  ...
}

}

所以在这个@PrepareForTest注释上SomeUnitTest会找到MockLibraryOne,并且那里的注释@PrepareForTest也会拖进来。Foo.classBar.class

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

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

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

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

4

4 回答 4

1

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

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

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

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

Powermock 不会更改 的代码java.lang.System.currentTimeMillis,因为它不能。相反,它会更改SystemClock's 字节码,使其不再调用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并单步执行。

Powermock 堆栈跟踪

如您所见,SystemClock调用了MockGateway.

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

调试变量

于 2020-07-19T07:58:47.673 回答
0

也许您正在寻找模拟政策

于 2013-11-01T21:28:40.720 回答
0

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

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

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

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

于 2019-12-27T15:58:25.207 回答
-1

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

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

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

于 2016-01-29T22:00:03.267 回答