Reputation: 2046
In my unit test I need to mock an interface which among different methods has nextItem()
and isEmpty()
methods:
public interface MyQueue {
Item nextItem();
boolean isEmpty();
//other methods
...
}
My requirement for the mock is that isEmpty()
initially should return false, but after nextItem()
was called isEmpty()
should return true. Thus I'm mocking a queue with one item.
nextItem()
second, third time and so on will result in a specific kind of exception?P.S. I don't want to provide the full implementation of my interface for the test, because of other methods in it, resulting in hard-to-understand and verbose code.
Upvotes: 8
Views: 18992
Reputation: 4295
You can make a utility method and then use it wherever you want.
public static boolean mockHasInvocation(Object mock, String methodName, Object... args) {
return mockingDetails(mock).getInvocations().stream()
.anyMatch(o -> o.getMethod().getName().equals(methodName) && Arrays.equals(o.getArguments(), args));
}
Simple usage:
if(mockHasInvocation(mockObject, "methodName", "argument1", "argument2")){doSomething();}
In this case you don't need any additional variables and it's more "Mockito style".
Upvotes: 1
Reputation: 452
I do realize that you explicitly wrote that you don't want to provide a full implementation of MyQueue, but, to be honest, that'd be the first thing I'd do.
In fact, I regularly provide 'mock' implementations of reasonably complex interfaces/objects for the purpose of making tests easier to test. I'm not the only one to think that: Spring Framework provides lots of mocked versions of complex objects (MockHttpServletRequest, MockHttpServletResponse, etc.), for example.
In that case, I'd avoid cluttering my test and provide this class either in a separate package or even in the production code.
A MockQueue would make your tests a lot more readable than the other (however correct) responses given here suggest.
Upvotes: 2
Reputation: 11805
You could provide some custom Answer implementations one of which depends on the other:
public class NextItemAnswer implements Answer<Item> {
private int invocationCount = 0;
private Item item;
public NextItemAnswer(Item item) {
this.item = item;
}
public Item answer(InvocationOnMock invocation) throws Throwable {
invocationCount++;
return item;
}
public int getInvocationCount() {
return invocationCount;
}
}
public class IsEmptyAnswer implements Answer<Boolean> {
private NextItemAnswer nextItemAnswer;
public IsEmptyAnswer(NextItemAnswer nextItemAnswer) {
this.nextItemAnswer = nextItemAnswer;
}
public Boolean answer(InvocationOnMock invocation) throws Throwable {
return nextItemAnswer.getInvocationCount() >= 0;
}
}
and then use it:
NextItemAnswer nextItemAnswer = new NextItemAnswer(item);
IsEmptyAnswer isEmptyAnswer = new IsEmptyAnswer(nextItemAnswer);
when(mock.isEmpty()).thenAnswer(isEmptyAnswer);
when(mock.nextItem()).thenAnswer(nextItemAnswer);
You might to tweak as i haven't tested this code, but the approach should be what you need.
Upvotes: 2
Reputation: 4403
You can achieve that with thenAnswer(), a feature Mockito documentation sees as controversial:
Yet another controversial feature which was not included in Mockito originally. We recommend using simple stubbing with toReturn() or toThrow() only. Those two should be just enough to test/test-drive any clean & simple code.
Here's thenAnswer:
private boolean called = false;
when(mock.nextItem()).thenAnswer(new Answer() {
Object answer(InvocationOnMock invocation) {
called = true;
return item;
}
when(mock.isEmpty()).thenAnswer(new Answer() {
Object answer(InvocationOnMock invocation) {
return called;
}
});
Upvotes: 10
Reputation: 340733
Here is a simple example:
//given
MyQueue mock = mock(MyQueue.class);
given(mock.isEmpty()).willReturn(false, true);
given(mock.nextItem()).willReturn(someItem);
//when
mock.isEmpty(); //yields false
mock.nextItem(); //yields someItem
mock.isEmpty(); //yields true
//then
InOrder inOrder = inOrder(mock);
inOrder.verify(mock).isEmpty();
inOrder.verify(mock).nextItem();
inOrder.verify(mock).isEmpty();
willReturn(false, true)
means: return false
on first invocation and true
on second. InOrder
object is used to verify invocation order. Change the order or remove nextItem()
call and the test will fail.
Alternatively you can use this syntax:
given(mock.isEmpty()).
willReturn(false).
willReturn(true).
willThrow(SpecialException.class);
If you need even stronger mocking semantics, you can introduce heavy artillery - custom answer callback:
given(mock.isEmpty()).willAnswer(new Answer<Boolean>() {
private int counter = 0;
@Override
public Boolean answer(InvocationOnMock invocation) throws Throwable {
switch(++counter) {
case 1: return false;
case 2: return true;
default: throw new SpecialException();
}
}
});
But this can easily lead to unmaintainable test code, use with caution.
Finally you can spy your real object by mocking only selected methods.
Upvotes: 6
Reputation: 41137
You can tell mockito to answer differently on successive calls to the same mocked method using the techniques described in the mockito docs.
when(mock.isEmpty())
.thenReturn(false)
.thenReturn(true);
will make the isEmpty()
call return true only on the first call, and
when(mock.nextItem())
.thenReturn(item)
.thenThrow(new NextOnEmptyQueueException())
will make nextItem()
return something on the first call and throw an exception on later calls.
I don't know that it's possible to make the result of one of these methods depend on sequencing of calls to the other though. If it is indeed possible, I'm sure it's a great deal more complex.
Upvotes: 1