pavel_kazlou
pavel_kazlou

Reputation: 2046

Mockito: method's return value depends on other method called

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.

  1. What is the simplest way to implement this kind of mock with mockito?
  2. Can I implement additional requirement: calling 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

Answers (6)

dvelopp
dvelopp

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

Eric
Eric

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

Matt
Matt

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

Assen Kolov
Assen Kolov

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

Tomasz Nurkiewicz
Tomasz Nurkiewicz

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

Don Roby
Don Roby

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

Related Questions