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

时间:2017-07-31 10:59:53

标签: java mocking mockito powermock powermockito

考虑以下(简化)枚举:

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()的实际实现。

我做错了什么?

3 个答案:

答案 0 :(得分:4)

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

    @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

答案 1 :(得分:3)

这是使用枚举超过"编译时间常数"的关键。 - 枚举类默认是最终的(你不能扩展MyEnum)。因此,在单元测试中处理它们可以 hard

@PrepareForTest意味着PowerMock将为带注释的类生成字节代码。但是你不能两种方式:要么生成(然后它不包含ONE,TWO,......)或者它是"真实&# 34; - 然后你就无法覆盖行为。

所以你的选择是:

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

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

答案 2 :(得分:0)

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

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