2011-03-09 87 views
15

我有下面的代碼我想測試:的Mockito測試無效方法

public class MessageService { 
    private MessageDAO dao; 

    public void acceptFromOffice(Message message) { 
     message.setStatus(0); 
     dao.makePersistent(message); 

     message.setStatus(1); 
     dao.makePersistent(message); 

    } 
    public void setDao (MessageDAO mD) { this.dao = mD; } 
} 

public class Message { 
    private int status; 
    public int getStatus() { return status; } 
    public void setStatus (int s) { this.status = s; } 

    public boolean equals (Object o) { return status == ((Message) o).status; } 

    public int hashCode() { return status; } 
} 

我需要驗證,該方法acceptFromOffice真的將狀態設置爲0,比堅持消息,然後恰克其狀態爲1,然後再堅持下去。

用的Mockito,我試圖做以下幾點:

@Test 
    public void testAcceptFromOffice() throws Exception { 

     MessageDAO messageDAO = mock(MessageDAO.class); 

     MessageService messageService = new MessageService(); 
     messageService.setDao(messageDAO); 

     final Message message = spy(new Message()); 
     messageService.acceptFromOffice(message); 

     verify(messageDAO).makePersistent(argThat(new BaseMatcher<Message>() { 
      public boolean matches (Object item) { 
       return ((Message) item).getStatus() == 0; 
      } 

      public void describeTo (Description description) { } 
     })); 

     verify(messageDAO).makePersistent(argThat(new BaseMatcher<Message>() { 
      public boolean matches (Object item) { 
       return ((Message) item).getStatus() == 1; 
      } 

      public void describeTo (Description description) { } 
     })); 

    } 

我居然想到這裏,覈查將覈查不同的消息對象的狀態調用makePersistent這個方法的兩倍。但它沒有說

爭論(s)是不同的!

任何線索?

+0

Mockito是一個需求?你可以建立自定義的TestMessage extends Message和MessageDAO類。如果這是一個選項,我可以寫一些代碼來說明。如果它不是我會:) – extraneon 2011-03-09 20:30:21

回答

23

測試你的代碼並不是微不足道的,儘管並非不可能。我的第一個想法是使用ArgumentCaptor,與ArgumentMatcher相比,使用和理解都容易得多。不幸的是,測試仍然失敗 - 原因肯定超出了這個答案的範圍,但如果你感興趣,我可能會提供幫助。不過我覺得這個測試案例很有趣,足以顯示(不正確的解決方案):

@RunWith(MockitoJUnitRunner.class) 
public class MessageServiceTest { 

    @Mock 
    private MessageDAO messageDAO = mock(MessageDAO.class); 

    private MessageService messageService = new MessageService(); 

    @Before 
    public void setup() { 
     messageService.setDao(messageDAO); 
    } 

    @Test 
    public void testAcceptFromOffice() throws Exception { 
     //given 
     final Message message = new Message(); 

     //when 
     messageService.acceptFromOffice(message); 

     //then 
     ArgumentCaptor<Message> captor = ArgumentCaptor.forClass(Message.class); 

     verify(messageDAO, times(2)).makePersistent(captor.capture()); 

     final List<Message> params = captor.getAllValues(); 
     assertThat(params).containsExactly(message, message); 

     assertThat(params.get(0).getStatus()).isEqualTo(0); 
     assertThat(params.get(1).getStatus()).isEqualTo(1); 
    } 

} 

不幸的是,工作方案需要比較複雜的使用Answer。簡而言之,不是讓Mockito記錄並驗證每個調用,而是提供一種回調方法,每次您的測試代碼執行給定的模擬時,都會執行這種方法。在此回調方法中(本例中爲MakePersistentCallback對象),您可以訪問兩個參數,並且可以更改返回值。這是一個沉重的大炮,你應該謹慎使用:

@Test 
    public void testAcceptFromOffice2() throws Exception { 
     //given 
     final Message message = new Message(); 
     doAnswer(new MakePersistentCallback()).when(messageDAO).makePersistent(message); 

     //when 
     messageService.acceptFromOffice(message); 

     //then 
     verify(messageDAO, times(2)).makePersistent(message); 
    } 


    private static class MakePersistentCallback implements Answer { 

     private int[] expectedStatuses = {0, 1}; 
     private int invocationNo; 

     @Override 
     public Object answer(InvocationOnMock invocation) throws Throwable { 
      final Message actual = (Message)invocation.getArguments()[0]; 
      assertThat(actual.getStatus()).isEqualTo(expectedStatuses[invocationNo++]); 
      return null; 
     } 
    } 

的例子是不完整的,但現在的測試成功,更重要的是,當您更改CUT幾乎所有的東西失敗。正如你所見,MakePersistentCallback.answer方法每次調用messageService.acceptFromOffice(message)被調用。在naswer內部,您可以執行所需的所有驗證。

注意:謹慎使用,維護此類測試可能會很麻煩,至少可以說。

+0

非常感謝。但實際上這不是我所需要的。因爲,即如果我需要驗證兩個makePersistance調用之間的某個呼叫,這將不起作用。我的意思是電話的順序不會被驗證,只有一些電話。調用如:verify(messageDAO).makePersistence(message);驗證(otherMock).smth();驗證(messageDAO)。makePersistence(消息);會在第一次驗證中引發異常,一次會得到2次makePersistence的調用 – glaz666 2011-03-09 20:04:00

+1

不是這樣,這段代碼驗證了'makePersistent'被調用了2次,第一次調用是帶有'Message'的狀態爲0,第二次調用了'Message '狀態爲1.如果您需要訂單驗證,請查看[InOrder](http://mockito.googlecode.com/svn/branches/1.8.5/javadoc/org/mockito/InOrder.html)。 – 2011-03-09 20:27:05

+0

好的,我明白了你的想法,但是在Mockito中做這種類型的東西非常尷尬。根據我在EasyMock的經驗,這裏記錄的行爲將會更加真實,可以這麼說。無論如何,謝謝 – glaz666 2011-03-10 08:53:46

0

您正在測試狀態機。用一些自定義實現來測試MessageService是很容易的。我認爲TestMessage將是最有趣的課程。

若要允許DAO /消息記錄持續調用,我做了一個自定義實現。

這不是Mockito,但它簡單,應該做的工作。

class TestMessageDAO implements MessageDAO { 
    // I have no clue what the MessageDAO does except for makePersistent 
    // which is the only relevant part here 

    public void makePersistent(Message message) { 
    if (message instanceof TestMessage) { 
     TestMessage test = (TestMessage)message; 
     test.persistCalled(); // will be recorded by TestMessage 
    } else { 
     throw RuntimeException("This test DAO does not support non-test messages"); 
    } 
    } 
} 

// Message isn't final so... 
class TestMessage extends Message { 
    enum state { 
    STARTED, STATUS0, PERSIST0, STATUS1, PERSIST1 
    } 

    public void persistCalled() { // For testing only 
    switch (state) { 
     case STATUS0: 
     state = PERSIST0; 
     break; 
     case STATUS1: 
     state = PERSIST1; 
     break; 
     default: 
     throw new RuntimeException("Invalid transition"); 
    } 
    } 

    public void setStatus(int status) { 
    switch(state) { 
     case STARTED: 
     if (status != 0) { 
      throw new IllegalArgumentException("0 required"); 
     } 
     state = STATUS0; 
     break; 
     case PERSIST0: 
     if (status != 1) { 
      throw new IllegalArgumentException("1 required"); 
     } 

     state = STATUS1; 
     break; 
     default: 
     throw new RuntimeException("Invalid transition"); 
    } 
    } 
} 

public class TestMessageService { 

    @Test 
    public void testService() { 
    MessageDAO dao = new TestMessageDAO(); 
    Message message = new TestMessage(); 
    MessageService service = new MessageService(); 
    service.setDao(dao); 
    service.acceptFromOffice(message); 
    } 

}