98

I'm not having any luck getting Mockito to capture function argument values! I am mocking a search engine index and instead of building an index, I'm just using a hash.

// Fake index for solr
Hashmap<Integer,Document> fakeIndex;

// Add a document 666 to the fakeIndex
SolrIndexReader reader = Mockito.mock(SolrIndexReader.class);

// Give the reader access to the fake index
Mockito.when(reader.document(666)).thenReturn(document(fakeIndex(666))

I can't use arbitrary arguments because I'm testing the results of queries (ie which documents they return). Likewise, I don't want to specify a specific value for and have a line for each document!

Mockito.when(reader.document(0)).thenReturn(document(fakeIndex(0))
Mockito.when(reader.document(1)).thenReturn(document(fakeIndex(1))
....
Mockito.when(reader.document(n)).thenReturn(document(fakeIndex(n))

I looked at the callbacks section on the Using Mockito page. Unfortunately, it isn't Java and I couldn't get my own interpretation of that to work in Java.

EDIT (for clarification): How do I get get Mockito to capture an argument X and pass it into my function? I want the exact value (or ref) of X passed to the function.

I do not want to enumerate all cases, and arbitrary argument won't work because I'm testing for different results for different queries.

The Mockito page says

val mockedList = mock[List[String]]
mockedList.get(anyInt) answers { i => "The parameter is " + i.toString } 

That's not java, and I don't know how to translate into java or pass whatever happened into a function.

Tom
  • 16,842
  • 17
  • 45
  • 54
nflacco
  • 4,972
  • 8
  • 45
  • 78
  • I'm not sure I understand exactly what is failing for you. Your call to `Mockito.when(reader.document(666)).thenReturn(document(fakeIndex(666))` should setup the mock object for you. What happens when you call `reader.document(666)`? – highlycaffeinated Jul 09 '11 at 00:08
  • The 666 works fine. However, I'd like to be able to pass in a specific number X and get the result of fakeIndex(X). I have a large number of potential docs to test for queries, and I don't want to enter them all. – nflacco Jul 09 '11 at 00:12

4 Answers4

115

I've never used Mockito, but want to learn, so here goes. If someone less clueless than me answers, try their answer first!

Mockito.when(reader.document(anyInt())).thenAnswer(new Answer() {
 public Object answer(InvocationOnMock invocation) {
     Object[] args = invocation.getArguments();
     Object mock = invocation.getMock();
     return document(fakeIndex((int)(Integer)args[0]));
     }
 });
Ed Staub
  • 15,480
  • 3
  • 61
  • 91
  • 2
    I just noticed the link on the right side to [Mockito: How to make a method return an argument that was passed to it](http://stackoverflow.com/questions/2684630/mockito-how-to-make-a-method-return-an-argument-that-was-passed-to-it). Looks like I'm close, if not spot on. – Ed Staub Jul 09 '11 at 00:45
  • strong user reputation (666) to original question correlation! That worked very well. Only change I made to get stuff compiling was put public in front of Object answer(InvocationOnMock invocation).... – nflacco Jul 09 '11 at 00:53
60

Check out ArgumentCaptors:

https://site.mockito.org/javadoc/current/org/mockito/ArgumentCaptor.html

ArgumentCaptor<Integer> argument = ArgumentCaptor.forClass(Integer.class);
Mockito.when(reader.document(argument.capture())).thenAnswer(
  new Answer() {
    Object answer(InvocationOnMock invocation) {
      return document(argument.getValue());
    }
  });
Sergey
  • 3,253
  • 2
  • 33
  • 55
qualidafial
  • 6,674
  • 3
  • 28
  • 38
53

You might want to use verify() in combination with the ArgumentCaptor to assure execution in the test and the ArgumentCaptor to evaluate the arguments:

ArgumentCaptor<Document> argument = ArgumentCaptor.forClass(Document.class);
verify(reader).document(argument.capture());
assertEquals(*expected value here*, argument.getValue());

The argument's value is obviously accessible via the argument.getValue() for further manipulation / checking or whatever you wish to do.

fl0w
  • 3,593
  • 30
  • 34
  • 2
    best answer: straight forward, easy to understand – Radu Cugut Sep 20 '19 at 19:08
  • Does not answer the question. Question is about Mockito.when and not verify. – seBaka28 Sep 07 '20 at 07:42
  • @seBaka28 the best solution to getting arguments is an argument captor. ArgumentCaptors are strongly advised to be used with verify by the authors of Mockito, thus I wanted to give a full perspective answer. if you for yourself choose not to use them, that is your choice, but not advised. EDIT: I don't see how this justifies a downvote, but that is also your choice to make. – fl0w Sep 07 '20 at 10:30
  • 2
    Because it does not answer the question. Yes, ArgumentCaptor is great when you want to capture the argument, but you can not use it in conjunction with when(...).thenReturn() which is what OP is trying to do: Based on a certain parameter, a mocked service is supposed to return a specific object. – seBaka28 Sep 09 '20 at 16:46
  • It is pecisely what is required here. you can do precisely this with Argument captors. Please just try it for yourself. – fl0w Sep 11 '20 at 09:47
  • What to do if the mock method is called several times? I get TooManyActualInvocations exception and don't know what to do with that. I need the latest invocation result – Alex Sep 14 '20 at 13:10
  • 1
    @YuraHoy that is a standard error message when you use verify and the object or method is called more often than you told verify to expect. You can change the expectation count by adding the times(n) argument as follows: verify(reader, times(5)) - this would expect 5 interactions. For reference please see: https://www.baeldung.com/mockito-verify – fl0w Sep 15 '20 at 06:37
  • and all the values of several invocations is available in your argument captor captor.getAllValues() – fl0w Sep 15 '20 at 14:06
18

With Java 8, this could be something like this:

Mockito.when(reader.document(anyInt())).thenAnswer(
  (InvocationOnMock invocation) -> document(invocation.getArguments()[0]));

I am assuming that document is a map.

Jean-François Beauchef
  • 1,291
  • 2
  • 11
  • 21