2013-03-19 70 views
69

使用MockIto初始化模擬對象的方法很多。 其中最好的方法是什麼?初始化模擬對象 - MockIto

1.

public class SampleBaseTestCase { 

    @Before public void initMocks() { 
     MockitoAnnotations.initMocks(this); 
    } 

2.

@RunWith(MockitoJUnitRunner.class) 

[編輯] 3.

mock(XXX.class); 

建議我,如果有任何其他的方式比這更好的... ...

回答

87

對於模擬初始化,使用亞軍或MockitoAnnotations.initMocks是嚴格等同的解決方案。從MockitoJUnitRunner的javadoc的:

JUnit 4.5 runner initializes mocks annotated with Mock, so that explicit usage of MockitoAnnotations.initMocks(Object) is not necessary. Mocks are initialized before each test method.


第一個解決方案(與MockitoAnnotations.initMocks)時,你已經在你的測試用例配置特定的亞軍(SpringJUnit4ClassRunner例如)可以使用。

第二個解決方案(與MockitoJUnitRunner)是更經典和我最喜歡的。代碼更簡單。使用跑步者提供了automatic validation of framework usage(由@David Wallacethis answer中描述)的巨大優勢。

這兩種解決方案都允許共享測試方法之間的模擬(和間諜)。再加上@InjectMocks,他們可以很快編寫單元測試。樣板模擬代碼被減少,測試更容易閱讀。例如:

@RunWith(MockitoJUnitRunner.class) 
public class ArticleManagerTest { 

    @Mock private ArticleCalculator calculator; 
    @Mock(name = "database") private ArticleDatabase dbMock; 
    @Spy private UserProvider userProvider = new ConsumerUserProvider(); 

    @InjectMocks private ArticleManager manager; 

    @Test public void shouldDoSomething() { 
     manager.initiateArticle(); 
     verify(database).addListener(any(ArticleListener.class)); 
    } 

    @Test public void shouldDoSomethingElse() { 
     manager.finishArticle(); 
     verify(database).removeListener(any(ArticleListener.class)); 
    } 
} 

優點:該代碼是最小的

缺點:妖術。 IMO主要是由於@InjectMocks註釋。有了這個註釋「你失去的代碼痛」(見@Brice偉大的評論)


第三種解決方案是在每個測試方法創建模擬。 它允許如@mlk在其答案中所解釋的那樣具有「自包含測試」。

public class ArticleManagerTest { 

    @Test public void shouldDoSomething() { 
     // given 
     ArticleCalculator calculator = mock(ArticleCalculator.class); 
     ArticleDatabase database = mock(ArticleDatabase.class); 
     UserProvider userProvider = spy(new ConsumerUserProvider()); 
     ArticleManager manager = new ArticleManager(calculator, 
                userProvider, 
                database); 

     // when 
     manager.initiateArticle(); 

     // then 
     verify(database).addListener(any(ArticleListener.class)); 
    } 

    @Test public void shouldDoSomethingElse() { 
     // given 
     ArticleCalculator calculator = mock(ArticleCalculator.class); 
     ArticleDatabase database = mock(ArticleDatabase.class); 
     UserProvider userProvider = spy(new ConsumerUserProvider()); 
     ArticleManager manager = new ArticleManager(calculator, 
                userProvider, 
                database); 

     // when 
     manager.finishArticle(); 

     // then 
     verify(database).removeListener(any(ArticleListener.class)); 
    } 
} 

優點:你清楚地表明如何你的API作品(BDD ...)

缺點:有更多的樣板代碼。 (該嘲笑創建)


recommandation是一種妥協。使用@Mock註釋與@RunWith(MockitoJUnitRunner.class),但不使用@InjectMocks

@RunWith(MockitoJUnitRunner.class) 
public class ArticleManagerTest { 

    @Mock private ArticleCalculator calculator; 
    @Mock private ArticleDatabase database; 
    @Spy private UserProvider userProvider = new ConsumerUserProvider(); 

    @Test public void shouldDoSomething() { 
     // given 
     ArticleManager manager = new ArticleManager(calculator, 
                userProvider, 
                database); 

     // when 
     manager.initiateArticle(); 

     // then 
     verify(database).addListener(any(ArticleListener.class)); 
    } 

    @Test public void shouldDoSomethingElse() { 
     // given 
     ArticleManager manager = new ArticleManager(calculator, 
                userProvider, 
                database); 

     // when 
     manager.finishArticle(); 

     // then 
     verify(database).removeListener(any(ArticleListener.class)); 
    } 
} 

優點:您清楚地表明您如何API的工作(我如何在ArticleManager實例化)。沒有樣板代碼。

缺點:測試不是自包含的代碼

+0

不過要小心,註釋是有用的,但他們不保護你免受到工藝OO設計不佳(或貶低它)。就個人而言,儘管我很樂意減少樣板代碼,但我放鬆了代碼(或PITA)的痛苦,這是將設計更改爲更好的代碼的原因,因此我和團隊正在關注OO設計。我覺得使用SOLID設計或GOOS思想等原理來進行OO設計是非常重要的,因爲它可以選擇如何實例化mock。 – Brice 2013-03-19 10:06:55

+0

@爲什麼?如果被測試的類('ArticleManager')具有太多依賴性,我會清楚地看到它。這是關於主題,問題只是「如何實例化模擬?」 – gontard 2013-03-19 10:07:07

+0

我應該說「不那麼明顯」:)所以實際上@injectMocks會嘗試最好自動注入模擬而不需要用戶連線(比如我們在春季或者guice中使用),所以對象實例化是隱藏的,你不知道它是構造器注入還是setter注入,這可能會對這個對象的未來使用造成麻煩(可重用性是良好OO設計的主要優點之一)。 – Brice 2013-03-19 10:50:39

9

痛苦小有這樣的一種巧妙的方法。

  • 如果它是一個單元測試,你可以這樣做:

    @RunWith(MockitoJUnitRunner.class) 
    public class MyUnitTest { 
    
        @Mock 
        private MyFirstMock myFirstMock; 
    
        @Mock 
        private MySecondMock mySecondMock; 
    
        @Spy 
        private MySpiedClass mySpiedClass = new MySpiedClass(); 
    
        // It's gonna inject the 2 mocks and the spied object per reflection to this object 
        // The java doc of @InjectMocks explains it really well how and when it does the injection 
        @InjectMocks 
        private MyClassToTest myClassToTest; 
    
        @Test 
        public void testSomething() { 
        } 
    } 
    
  • 編輯:如果它是一個集成測試,你可以做到這一點(不打算使用與Spring這樣就也展示了你可以初始化diferent運動員嘲笑):

    @RunWith(SpringJUnit4ClassRunner.class) 
    @ContextConfiguration("aplicationContext.xml") 
    public class MyIntegrationTest { 
    
        @Mock 
        private MyFirstMock myFirstMock; 
    
        @Mock 
        private MySecondMock mySecondMock; 
    
        @Spy 
        private MySpiedClass mySpiedClass = new MySpiedClass(); 
    
        // It's gonna inject the 2 mocks and the spied object per reflection to this object 
        // The java doc of @InjectMocks explains it really well how and when it does the injection 
        @InjectMocks 
        private MyClassToTest myClassToTest; 
    
        @Before 
        public void setUp() throws Exception { 
          MockitoAnnotations.initMocks(this); 
        } 
    
        @Test 
        public void testSomething() { 
        } 
    } 
    
+1

如果MOCK也參與集成測試,它是否合理? – VinayVeluri 2013-03-19 12:42:00

+1

其實它不會,你的權利。我只是想展示Mockito的可能性。例如,如果你使用RESTFuse你必須使用他們的跑步者,所以你可以用MockitoAnnotations來初始化模擬。initMocks(本); – emd 2013-03-19 12:48:50

5

MockitoAnnotations &的亞軍得到了很好的上面所討論的,所以我要在我的兩便士扔了沒人愛:

XXX mockedXxx = mock(XXX.class); 

我用這個,因爲我覺得它有點更具描述性的,我更喜歡(不出來正確的禁令)單元測試不使用成員變量,因爲我喜歡我的測試(儘可能多)自我包含。

+0

除了使測試用例自包含之外,是否還有使用模擬(XX.class)的其他優勢? – VinayVeluri 2013-03-19 12:43:41

+0

不是我所知道的。 – 2013-03-19 14:12:22

+2

爲了閱讀測試需要了解更少的魔法。你聲明變量,並給它一個值 - 沒有註釋,反射等。 – Karu 2015-06-02 04:14:09

17

現在有(第1.10.7節)實例化mock的第四種方式,它使用JUnit4 規則,稱爲MockitoRule

@RunWith(JUnit4.class) // or a different runner of your choice 
public class YourTest 
    @Rule public MockitoRule rule = MockitoJUnit.rule(); 
    @Mock public YourMock yourMock; 

    @Test public void yourTestMethod() { /* ... */ } 
} 

的JUnit尋找subclasses of TestRule annotated with @Rule,並把它們用來包裝測試語句的轉輪提供。這樣做的結果是你可以提取@Before方法,@After方法,甚至嘗試......將包裝器捕獲到規則中。你甚至可以在你的測試中與這些進行交互,就像ExpectedException那樣。

MockitoRule行爲幾乎完全一樣MockitoJUnitRunner,但你可以使用任何其他選手,如Parameterized(它允許你的測試構造帶參數,以便您的測試可以運行多次),或Robolectric的測試運行(這樣它的類加載器可以爲Android本地類提供Java替換)。這使得它在最近的JUnit和Mockito版本中使用起來更爲靈活。

總結:

  • Mockito.mock():沒有註解支持或使用驗證直接調用。
  • MockitoAnnotations.initMocks(this):註釋支持,沒有使用驗證。
  • MockitoJUnitRunner:註釋支持和使用驗證,但您必須使用該運行器。
  • MockitoRule:使用任何JUnit運行器進行註釋支持和使用驗證。

參見:How JUnit @Rule works?