Warning: file_get_contents(/data/phpspider/zhask/data//catemap/9/java/329.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Java powermockito:如何在枚举中模拟抽象方法_Java_Mocking_Mockito_Powermock_Powermockito - Fatal编程技术网

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

Java powermockito:如何在枚举中模拟抽象方法,java,mocking,mockito,powermock,powermockito,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(); } 这在以下函数中使用: voi

考虑以下(简化)枚举:

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()
的真正实现


我做错了什么?

这是将枚举用于多个“编译时常量”的关键所在-默认情况下,枚举类是最终类(不能扩展MyEnum)。所以在单元测试中处理它们可能很困难

@PrepareForTest意味着PowerMock将为注释类生成字节码。但是你不能同时拥有它:要么类被生成(然后它不包含一个,两个,…),要么它是“真实的”——然后你不能重写行为

因此,您的选择是:

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

我强烈推荐选项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);
   }
}
正如您所看到的,测试非常简短。但是,我建议不要模拟枚举,除非您有明确的需要这样做。不要因为可以做到就嘲笑它

  • 枚举中的每个常量都是静态的最终嵌套类。所以要模拟它,必须在PrepareForTest中指向嵌套类
  • MyEnum.values()
    返回预先初始化的数组,因此在您的示例中它也应该是模拟的
  • 每个枚举常量都只是
    公共最终静态
    字段
  • 总而言之:

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

    我认为这不可能。PowerMock不会生成字节码。它不使用代理作为EasyMock或Mockito。它修改字节码。例如,在enum案例中,PowerMock做了两件事:删除最后一个修饰符,并在每个方法的开头插入一条指令。@ArthurZagretdinov我感谢您的纠正。但我想知道。当您执行
    mockStatic(Foo.class)
    时,我的假设是PowerMock首先检查Foo的方法签名,然后根据它生成一些东西。也许这只是吹毛求疵。你看:为了修改某个东西,你必须A)拿走某个东西B)用新的东西(为此目的而生成)替换它。然后调用
    mockStatic
    ,然后使用两个模拟框架之一创建一个新的mock for object Foo.class,并在存储库中注册。代码插入在静态方法的开头,检查是否存在此类的mock。如果没有,则继续正常执行。如果存在,则调用mocking框架引擎以获得存根响应。我认为您应该更经常地在这里发布。围绕PowerMock有许多有趣的奇怪问题;-)@我认识一只幽灵猫。我试过了,但我更专注于开发新版本的PowerMock,这将有助于解决一些老问题,比如代码覆盖率。很高兴听到这个消息。为了防止误解:我倾向于建议人们不要使用PowerMock。不是因为它是一个糟糕的框架,而是因为我有过太多的人不考虑他们的设计的经验;然后,他们并没有改进设计,而是用那个巨大的PowerMock锤子来“解决”因设计不灵活而引起的症状。@Arthur Zagretdinov。这看起来是我需要的解决方案。是否有可能提供工作样品解决方案?我这样问是因为我认为我已经听从了你的建议,但我所嘲笑的方法仍然在被调用!我添加了完整的示例。