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

powermockito: How to mock abstract method in enum

考虑以下(简化的)枚举:

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

    TWO public int myMethod() {
        // Some complex stuff
        return 2;
    };

    public abstract int myMethod();
}

这在函数中使用,例如:

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

我现在想为 consumer 编写一个单元测试,模拟每个枚举实例中对 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() 的真正实现被调用了。

我做错了什么?

这是使用枚举超过 "compile time constants" 的关键 - 枚举 class 默认情况下是最终的(您不能扩展 MyEnum)。因此在单元测试中处理它们可能困难

@PrepareForTest 表示 PowerMock 将为带注释的 class 生成字节码。但是你不能同时拥有它:要么 class 是 生成的 (那么它不包含一个,两个,......)或者它是 "real" - 然后你不能覆盖行为。

所以你的选择是:

  • mock 整个 class,然后看看你是否可以通过某种方式获得 values() 到 return 模拟枚举 class 对象的列表(参见 第一部分 )
  • 退一步改进您的设计。示例:您可以创建一个表示 myMethod()interface 并让您的枚举实现它。然后你不直接使用 values() - 而是引入某种工厂,它只是 return 一个 List<TheNewInterface> - 然后工厂可以 return 模拟对象列表用于单元测试。

我强烈推荐选项 2 - 因为这也会 提高 代码库的质量(通过切断与枚举 class 及其常量的紧密耦合您的代码当前处理)。

根据我对 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);
   }
}

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

  1. 枚举中的每个常量都是嵌套的静态最终 class。因此,要模拟它,您必须在 PrepareForTest.
  2. 中嵌套 class
  3. MyEnum.values() returns 预初始化数组,因此在您的情况下它也应该是模拟的。
  4. 每个 Enum 常量只是 public final static 字段。

总计:

@RunWith(PowerMockRunner.class)
@PrepareForTest(
value = MyEnum.class,
fullyQualifiedNames = {
                          "com.Whosebug.q45414070.MyEnum",
                          "com.Whosebug.q45414070.MyEnum"
})

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);
  }

}

Full example