Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Capture an argument in Mockito

I'm testing a certain class. This class is internally instantiating a "GetMethod" object that gets passed to a "HttpClient" object that gets injected into the tested class.

I'm mocking the "HttpClient" class, but I would need to modify the behaviour of one method of the "GetMethod" class too. I'm playing with ArgumentCaptor but I don't seem to be able to get a hold of the instantiated object in the "when" call.

Example:

HttpClient mockHttpClient = mock(HttpClient.class);
ArgumentCaptor<GetMethod> getMethod = ArgumentCaptor.forClass(GetMethod.class);
when(mockHttpClient.executeMethod(getMethod.capture())).thenReturn(HttpStatus.SC_OK);
when(getMethod.getValue().getResponseBodyAsStream()).thenReturn(new FileInputStream(source));

Response:

org.mockito.exceptions.base.MockitoException: 
No argument value was captured!
You might have forgotten to use argument.capture() in verify()...
...or you used capture() in stubbing but stubbed method was not called.
Be aware that it is recommended to use capture() only with verify()
like image 842
Iker Jimenez Avatar asked Sep 01 '10 17:09

Iker Jimenez


People also ask

What is capture in mockito?

Mockito ArgumentCaptor is used to capture arguments for mocked methods. ArgumentCaptor is used with Mockito verify() methods to get the arguments passed when any method is called. This way, we can provide additional JUnit assertions for our tests.

What does argument capture do?

ArgumentCaptor allows us to capture an argument passed to a method to inspect it. This is especially useful when we can't access the argument outside of the method we'd like to test.

What is used to return all captured values in mockito?

We use argument captor with the methods like verify() or then() to get the values passed when a specific method is invoked. It is used to capture the method arguments. It is used to build a new ArgumentCaptor. It is used to return all the captured values.

Which annotation is used to capture the values in the method?

The @Captor annotation is used to create an ArgumentCaptor instance which is used to capture method argument values for further assertions.


2 Answers

You cant use when on getMethod, because getMethod is not a mock. It is still real object created by your class.

ArgumentCaptor has quite different purpose. Check section 15 here.

You could make your code more testable. Generally, classes that are creating new instances of other classes are difficult to test. Put some factory to this class for creating get/post methods, then in test mock this factory, and make it mock get/post methods.

public class YourClass {
  MethodFactory mf;

  public YourClass(MethodFactory mf) {
    this.mf = mf;
  }

  public void handleHttpClient(HttpClient httpClient) {
    httpClient.executeMethod(mf.createMethod());
    //your code here
  }
}

Then in test you can do:

HttpClient mockHttpClient = mock(HttpClient.class);
when(mockHttpClient.executeMethod(any(GetMethod.class)).thenReturn(HttpStatus.SC_OK);

MethodFactory factory = mock(MethodFactory.class);
GetMethod get = mock(GetMethod.class);
when(factory.createMethod()).thenReturn(get);
when(get.getResponseBodyAsStream()).thenReturn(new FileInputStream(source));

UPDATE

You can also try some nasty hack, and Answer and accessing GetMethod's private parts ;) by reflection. (This is really nasty hack)

when(mockHttpClient.executeMethod(any(GetMethod.class))).thenAnswer(new Answer() {
  Object answer(InvocationOnMock invocation) {
    GetMethod getMethod = (GetMethod) invocation.getArguments()[0];

    Field respStream = HttpMethodBase.class.getDeclaredField("responseStream");
    respStream.setAccessible(true);
    respStream.set(getMethod, new FileInputStream(source));

    return HttpStatus.SC_OK;
  }
});
like image 109
amorfis Avatar answered Sep 17 '22 17:09

amorfis


Ok, this is how I've solved it. A little bit convoluted but couldn't find any other way.

In the test class:

private GetMethod getMethod;

public void testMethod() {
    when(mockHttpClient.executeMethod(any(GetMethod.class))).thenAnswer(new ExecuteMethodAnswer());
    //Execute your tested method here.
    //Acces the getMethod here, assert stuff against it.  
}

private void setResponseStream(HttpMethodBase httpMethod, InputStream inputStream) throws NoSuchFieldException, IllegalAccessException {
    Field privateResponseStream = HttpMethodBase.class.getDeclaredField("responseStream");
    privateResponseStream.setAccessible(true);
    privateResponseStream.set(httpMethod, inputStream);
}

private class ExecuteMethodAnswer implements Answer {
    public Object answer(InvocationOnMock invocation) throws FileNotFoundException,
                                                             NoSuchFieldException, IllegalAccessException {
        getMethod = (GetMethod) invocation.getArguments()[0];
        setResponseStream(getMethod, new FileInputStream(source));
        return HttpStatus.SC_OK;
    }
}
like image 26
Iker Jimenez Avatar answered Sep 19 '22 17:09

Iker Jimenez