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);
}
}
正如您所看到的,测试非常简短。但是,我建议不要模拟枚举,除非您有明确的需要这样做。不要因为可以做到就嘲笑它
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。这看起来是我需要的解决方案。是否有可能提供工作样品解决方案?我这样问是因为我认为我已经听从了你的建议,但我所嘲笑的方法仍然在被调用!我添加了完整的示例。