powermockito: How to mock abstract method in enum

流过昼夜 提交于 2019-11-29 15:43:38
  1. Each constant in enum it's a static final nested class. So to mock it you have to pointe nested class in PrepareForTest.
  2. MyEnum.values() returns pre-initialised array, so it should be also mock in your case.
  3. 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

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 use values() directly - instead you introduce some kind of factory that simply returns a List<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).

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.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!