2017-07-31 53 views
2

考虑以下(简化)枚举: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()真正的实现都被调用。

我做错了什么?

+0

我不认为这是可能的。 – 2017-07-31 11:18:31

回答

3
  1. 枚举中的每个常量它是一个静态的最终嵌套类。所以为了嘲笑它,你必须在PrepareForTest中使用pointe嵌套类。
  2. MyEnum.values()返回预先初始化的数组,所以它也应该模拟你的情况。
  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); 
    } 

} 

Full example

+0

我认为你应该更频繁地在这里发帖。围绕PowerMock有许多有趣的奇怪问题;-)我知道的@GhostCat – GhostCat

+0

。我尝试了,但我更关注开发PowerMock的新版本,这将有助于解决代码覆盖等一些旧问题。 –

+0

很高兴听到这个消息。只是为了防止误解:我倾向于建议人们不要使用PowerMock。不是因为它是一个糟糕的框架 - 而是因为我创造了太多人不会考虑其设计的经验;然后他们不用改进设计,而是使用PowerMock大锤来“解决”由不灵活设计引起的症状。 – GhostCat

2

这是使用枚举超过“编译时间常量”的症结 - 枚举类默认是最终的(不能扩展MyEnum)。因此在单元测试中处理它们可以是

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

那么你的选择是:

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

我强烈建议选择2 - 作为也将提高您的代码库的质量(通过切割紧耦合到枚举类及其常量您的代码目前与交易)。

+0

PowerMock不会生成字节码。它不像EasyMock或Mockito那样使用代理。它修改字节码。举例来说,在枚举情况下,PowerMock会做两件事:删除最终修饰符,并在每个方法的开始处插入一条指令。 –

+0

@ArthurZagretdinov我很欣赏这个更正。但我想知道。当你执行'mockStatic(Foo.class)'时 - 我的假设是PowerMock **首先**检查Foo的方法签名 - 然后*生成*基于此的东西。也许这只是挑剔的。你会看到:为了修改某些东西,你必须:A)带走某些东西B)用新的东西替换它(为此目的而生成的东西)。 – GhostCat

+0

然后调用'mockStatic',然后为对象创建一个新的模拟Foo.class,使用两个模拟框架之一创建并注册到一个存储库中。该代码插入在静态方法开始时,检查是否存在或不存在该类的模拟。否则,继续正常执行。如果存在,则调用嘲笑框架引擎来获取存根响应。 –

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

正如你可以看到,测试是很简短。不过,除非你有明确的要求,否则我会推荐而不是模仿enum。不要因为它可以完成而嘲笑它。