问题
Consider the following (simplified) enumeration:
MyEnum {
ONE public int myMethod() {
// Some complex stuff
return 1;
},
TWO public int myMethod() {
// Some complex stuff
return 2;
};
public abstract int myMethod();
}
This is used in a function like:
void consumer() {
for (MyEnum n : MyEnum.values()) {
n.myMethod();
}
}
I'd now like to write a unit test for consumer
that mocks out the calls to myMethod() in each of the enumeration instances. I've tried the following:
@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()
}
But the real implementations of ONE.myMethod()
and TWO.myMethod()
are being called.
What have I done wrong?
回答1:
- Each constant in enum it's a static final nested class. So to mock it you have to pointe nested class in PrepareForTest.
MyEnum.values()
returns pre-initialised array, so it should be also mock in your case.- Each Enum constant it is just
public final static
field.
All together:
@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
回答2:
That is the crux with using enums for more than "compile time constants" - enum classes are final by default (you can't extend MyEnum). So dealing with them within unit test can be hard.
@PrepareForTest means that PowerMock will generate byte code for the annotated class. But you can't have it both ways: either the class is generated (then it doesn't contain ONE, TWO, ...) or it is "real" - and then you can't override behavior.
So your options are:
- mock the whole class, and then see if you can somhow get
values()
to return a list of mocked enum class objects ( see here for the first part ) - step back and improve your design. Example: you could create an interface that denotes
myMethod()
and have your enum implement that. And then you don't usevalues()
directly - instead you introduce some kind of factory that simply returns aList<TheNewInterface>
- and then the factory can return a list of mocked objects for your unit test.
I strongly recommend option 2 - as that will also improve the quality of your code base (by cutting the tight coupling to the enum class and its constants that your code currently deals with).
回答3:
From what I know about PowerMock, your test should work as is. Maybe you could open an issue in the PowerMock github project?
Anyway, here is a self-contained test that does work, but using another library, 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);
}
}
As you can see, the test is quite short and simple. However, I would recommend to not mock an enum unless you have a clear need to do so. Don't mock it just because it can be done.
来源:https://stackoverflow.com/questions/45414070/powermockito-how-to-mock-abstract-method-in-enum