[英]Mockito to test void methods
我有以下要測試的代碼:
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) { }
}));
}
我實際上在這里期望驗證將驗證使用不同Message對象的狀態調用兩次makePersistent方法。 但它沒有說出來
爭論是不同的!
有線索嗎?
測試代碼並非易事,但並非不可能。 我的第一個想法是使用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記錄並驗證每次調用,而是提供一種回調方法,每次執行測試代碼時都會執行mock。 在這個回調方法(在我們的示例中為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中更改幾乎任何內容時都會失敗。 如您所見,每次調用messageService.acceptFromOffice(message)
都會調用MakePersistentCallback.answer
方法。 在naswer
您可以執行所需的所有驗證。
注意:謹慎使用,維持此類測試至少可以說是麻煩。
您正在測試狀態機。 使用一些自定義實現測試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);
}
}
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.