2017-07-31 52 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。不要因爲它可以完成而嘲笑它。