powermockito:如何在枚举中模拟抽象方法

考虑以下(简化)枚举:

MyEnum { ONE public int myMethod() { // Some complex stuff return 1; }, TWO public int myMethod() { // Some complex stuff return 2; }; public abstract int myMethod(); } 

这用于以下function:

 void consumer() { for (MyEnum n : MyEnum.values()) { n.myMethod(); } } 

我现在想为consumer编写一个unit testing,在每个枚举实例中模拟对myMethod()的调用。 我尝试过以下方法:

 @RunWith(PowerMockRunner.class) @PrepareForTest(MyEnum.class) public class MyTestClass { @Test public void test() throws Exception { mockStatic(MyEnum.class); when(MyEnum.ONE.myMethod()).thenReturn(10); when(MyEnum.TWO.myMethod()).thenReturn(20); // Now call consumer() } 

但是正在调用ONE.myMethod()TWO.myMethod()的实际实现。

我做错了什么?

  1. 枚举中的每个常量都是静态的最终嵌套类。 所以要模拟它,你必须在PrepareForTest中使用嵌套类。
  2. MyEnum.values()返回预初始化的数组,所以在你的情况下它也应该是mock。
  3. 每个枚举常数只是public final static字段。

全部一起:

 @RunWith(PowerMockRunner.class) @PrepareForTest( value = MyEnum.class, fullyQualifiedNames = { "com.stackoverflow.q45414070.MyEnum$1", "com.stackoverflow.q45414070.MyEnum$2" }) public class MyTestClass { @Test public void should_return_sum_of_stubs() throws Exception { final MyEnum one = mock(MyEnum.ONE.getClass()); final MyEnum two = mock(MyEnum.TWO.getClass()); mockStatic(MyEnum.class); when(MyEnum.values()).thenReturn(new MyEnum[]{one, two}); when(one.myMethod()).thenReturn(10); when(two.myMethod()).thenReturn(20); assertThat(new Consumer().consumer()) .isEqualTo(30); } @Test public void should_return_stubs() { final MyEnum one = mock(MyEnum.ONE.getClass()); when(one.myMethod()).thenReturn(10); Whitebox.setInternalState(MyEnum.class, "ONE", one); assertThat(MyEnum.ONE.myMethod()).isEqualTo(10); } } 

完整的例子

这是使用枚举超过“编译时常量”的关键 – 默认情况下枚举类是最终的(你不能扩展MyEnum)。 因此,在unit testing中处理它们可能很难

@PrepareForTest意味着PowerMock将为带注释的类生成字节代码。 但你不能两种方式: 生成类(然后它不包含ONE,TWO,……)或它是“真实的” – 然后你不能覆盖行为。

所以你的选择是:

  • 模拟整个类,然后查看是否可以somhow获取values()以返回模拟的枚举类对象列表(请参阅此处的第一部分)
  • 退一步,改进你的设计。 示例:您可以创建一个表示myMethod()接口 ,并让您的枚举实现它。 然后你不直接使用values() – 而是引入某种只返回List的工厂 – 然后工厂可以为你的unit testing返回一个List对象列表。

我强烈建议选项2 – 因为这也将提高代码库的质量(通过减少与枚举类及其代码当前处理的常量的紧密耦合)。

根据我对PowerMock的了解,您的测试应该按原样运行。 也许你可以在PowerMock github项目中打开一个问题?

无论如何,这是一个自包含的测试, 它可以工作,但使用另一个库,JMockit:

 public final class MockingAnEnumTest { public enum MyEnum { ONE { @Override public int myMethod() { return 1; } }, TWO { @Override public int myMethod() { return 2; } }; public abstract int myMethod(); } int consumer() { int res = 0; for (MyEnum n : MyEnum.values()) { int i = n.myMethod(); res += i; } return res; } @Test public void mocksAbstractMethodOnEnumElements() { new Expectations(MyEnum.class) {{ MyEnum.ONE.myMethod(); result = 10; MyEnum.TWO.myMethod(); result = 20; }}; int res = consumer(); assertEquals(30, res); } } 

如您所见,测试非常简短。 但是,我建议不要嘲笑枚举,除非你明确需要这样做。 不要因为可以做到而嘲笑它。