【问题标题】:powermockito: How to mock abstract method in enumpowermockito:如何在枚举中模拟抽象方法
【发布时间】:2017-07-31 10:59:52
【问题描述】:

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

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() 的真正实现正在被调用。

我做错了什么?

【问题讨论】:

  • 我认为这不可能。

标签: java mocking mockito powermock powermockito


【解决方案1】:
  1. 枚举中的每个常量都是一个静态最终嵌套类。因此,要模拟它,您必须在 PrepareForTest 中指出嵌套类。
  2. MyEnum.values() 返回预初始化数组,因此在您的情况下它也应该是模拟的。
  3. 每个 Enum 常量都只是 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);
  }

}

Full example

【讨论】:

  • 我认为你应该经常在这里发帖。 PowerMock 周围有很多有趣的奇怪问题 ;-)
  • @GhostCat 我知道。我尝试了,但我更专注于开发新版本的 PowerMock,这将有助于解决代码覆盖等几个旧问题。
  • 很高兴听到这个消息。并且只是为了防止误解:我倾向于建议人们不要使用 PowerMock。不是因为它是一个糟糕的框架——而是因为我有太多人不考虑他们的设计的经验;然后,他们没有改进设计,而是使用 PowerMock 大锤来“解决”由不灵活的设计引起的症状。
  • @Arthur Zagretdinov。这看起来像我需要的解决方案。是否有机会提供有效的示例解决方案?我之所以问,是因为我认为我听从了你的建议,但我嘲笑的方法仍然被调用!
  • 我已经添加了完整的例子。
【解决方案2】:

这是将枚举用于“编译时常量”之外的关键——枚举类默认是最终的(您不能扩展 MyEnum)。所以在单元测试中处理它们可能是困难

@PrepareForTest 表示 PowerMock 将为带注释的类生成字节码。但是你不能同时拥有它:要么类是生成的(那么它不包含 ONE、TWO、...)或者它是“真实的” - 然后你不能覆盖行为。

所以你的选择是:

  • 模拟整个类,然后看看你是否可以让values() 返回一个模拟的枚举类对象列表(第一部分见here
  • 退后一步,改进您的设计。示例:您可以创建一个表示 myMethod()interface 并让您的枚举实现它。然后你不直接使用values()——而是引入某种工厂,它只返回一个List<TheNewInterface>——然后工厂可以为你的单元测试返回一个模拟对象列表。

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

【讨论】:

  • PowerMock 不生成字节码。它不使用代理作为 EasyMock 或 Mockito。它修改字节码。例如,在枚举情况下,PowerMock 做了两件事:删除 final 修饰符并在每个方法的开头注入一条指令。
  • @ArthurZagretdinov 感谢您的更正。但我想知道。当您执行mockStatic(Foo.class) 时 - 我的假设是 PowerMock 首先 检查 Foo 的方法签名 - 然后基于此 generate 一些东西。也许这只是在这里挑剔。您会看到:为了修改某些东西,您必须 A) 拿走某些东西 B) 用新的东西(为此目的而生成)替换它。
  • 然后调用mockStatic,然后使用两个模拟框架之一创建对象 Foo.class 的新模拟并在存储库中注册。在静态方法的开头插入的代码检查这个类是否存在。如果没有,则继续正常执行。如果存在,则调用 mocking 框架引擎获取 stub 响应。
【解决方案3】:

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

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-08-31
    • 2022-06-25
    • 2022-07-29
    • 1970-01-01
    • 1970-01-01
    • 2011-11-16
    • 1970-01-01
    相关资源
    最近更新 更多