Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Mockito to test void methods

Tags:

I have following code I want to test:

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; } } 

I need to verify, that method acceptFromOffice really sets status to 0, than persist message, then chage its status to 1, and then persist it again.

With Mockito, I have tried to do following:

@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) { }         }));      } 

I actually expect here that verification will verify calling twice of makePersistent method with a different Message object's state. But it fails saying that

Argument(s) are different!

Any clues?

like image 572
glaz666 Avatar asked Mar 09 '11 17:03

glaz666


People also ask

What assert in void method?

Whenever we write unit test cases for any method we expect a return value from the method and generally use assert for checking if the functions return the value that we expect it to return, but in the case of void methods, they do not return any value.

What is doNothing in Mockito?

However, doNothing() is Mockito's default behavior for void methods. This version of whenAddCalledVerified() accomplishes the same thing as the one above: @Test public void whenAddCalledVerified() { MyList myList = mock(MyList.class); myList.add(0, ""); verify(myList, times(1)).add(0, ""); }

How do you mock a private method?

For Mockito, there is no direct support to mock private and static methods. In order to test private methods, you will need to refactor the code to change the access to protected (or package) and you will have to avoid static/final methods.


1 Answers

Testing your code is not trivial, though not impossible. My first idea was to use ArgumentCaptor, which is much easier both to use and comprehend compared to ArgumentMatcher. Unfortunately the test still fails - reasons are certainly beyond the scope of this answer, but I may help if that interests you. Still I find this test case interesting enough to be shown (not correct solution):

@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);     }  } 

Unfortunately the working solution requires somewhat complicated use of Answer. In a nutshell, instead of letting Mockito to record and verify each invocation, you are providing sort of callback method that is executed every time your test code executes given mock. In this callback method (MakePersistentCallback object in our example) you have access both to parameters and you can alter return value. This is a heavy cannon and you should use it with care:

    @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;         }     } 

The example is not complete, but now the test succeeds and, more importantly, fails when you change almost anything in CUT. As you can see MakePersistentCallback.answer method is called every time mocked messageService.acceptFromOffice(message) is called. Inside naswer you can perform all the verifications you want.

NB: Use with caution, maintaining such tests can be troublesome to say the least.

like image 112
Tomasz Nurkiewicz Avatar answered Sep 16 '22 14:09

Tomasz Nurkiewicz