views:

78

answers:

2

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()
+2  A: 

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: http://mockito.googlecode.com/svn/tags/latest/javadoc/org/mockito/Mockito.html

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;
  }
});
amorfis
I know instantiation makes classes difficult to test, but in this case a factory would be overkill, and I'm not free to change the implementation too much.
Iker Jimenez
A: 

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;
    }
}
Iker Jimenez
You posted it when I was editing my answer. Well, we both resolved it the same way :)
amorfis
Yes, I cannot find any other way of doing it with the available tools. Nasty hack :) but it rocks when it works!
Iker Jimenez