Mockito doReturn/when executes real method (without CGILIB)

7.3k views Asked by At

I have done many Mockito spy and just imitating my own working examples in a new project. But this failed miserably

import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.StatusLine;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.impl.client.DefaultHttpClient;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;

import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

import static org.mockito.Mockito.*;

public class SpyTest {
    HttpClient httpClient;
    private HttpResponse httpResponse;
    private StatusLine responseStatus;
    private HttpEntity responseEntity;

    @BeforeMethod
    public void setupClient() throws Exception {
        List spy = spy(new ArrayList());
        doReturn(true).when(spy).addAll(any(Collection.class)); // this works!

        DefaultHttpClient directClient = new DefaultHttpClient();
        httpClient = spy(directClient);
        httpResponse = mock(HttpResponse.class);
        responseStatus = mock(StatusLine.class);
        responseEntity = mock(HttpEntity.class);
        doReturn(responseStatus).when(httpResponse).getStatusLine();
        doReturn(responseEntity).when(httpResponse).getEntity();
        doReturn(httpResponse).when(httpClient).execute(any(HttpPost.class)); // failing here
    }

    @Test
    public void itShouldSetupTheSpy() throws Exception {
        doReturn(200).when(responseStatus).getStatusCode();
        doReturn("OK").when(responseStatus).getReasonPhrase();
        doAnswer(new Answer() {
            @Override
            public Object answer(InvocationOnMock invocation) throws Throwable {
                OutputStream os = (OutputStream) invocation.getArguments()[0];
                os.write("{\"id\": 100}".getBytes());
                return null;
            }
        }).when(responseEntity).writeTo(any(OutputStream.class));
    }
}

But I got error

    java.lang.IllegalArgumentException: Request must not be null.
    at org.apache.http.impl.client.AbstractHttpClient.execute(AbstractHttpClient.java:801)
    at org.apache.http.impl.client.AbstractHttpClient.execute(AbstractHttpClient.java:784)
    at SpyTest.setupClient(SpyTest.java:37)

I believe I have followed the official advice! closely, like my other spies. And this answer! does not seem to apply to me since I don't use CGLIB as evident from the maven dependency tree:


    [INFO] +- org.codehaus.jackson:jackson-mapper-asl:jar:1.9.3:compile
    [INFO] |  \- org.codehaus.jackson:jackson-core-asl:jar:1.9.3:compile
    [INFO] +- org.testng:testng:jar:6.8:test
    [INFO] |  +- junit:junit:jar:4.10:test
    [INFO] |  |  \- org.hamcrest:hamcrest-core:jar:1.1:test
    [INFO] |  +- org.beanshell:bsh:jar:2.0b4:test
    [INFO] |  +- com.beust:jcommander:jar:1.27:test
    [INFO] |  \- org.yaml:snakeyaml:jar:1.6:test
    [INFO] +- org.mockito:mockito-all:jar:1.9.5:test
    [INFO] +- com.newrelic:newrelic-api:jar:2.3.0:compile
    [INFO] +- org.apache.httpcomponents:httpclient:jar:4.2.5:compile
    [INFO] |  +- org.apache.httpcomponents:httpcore:jar:4.2.4:compile
    [INFO] |  +- commons-logging:commons-logging:jar:1.1.1:compile
    [INFO] |  \- commons-codec:commons-codec:jar:1.6:compile
    [INFO] \- org.apache.commons:commons-lang3:jar:3.1:compile

It looks like having problem with the HttpClient and its subclasses.

1

There are 1 answers

1
Jeff Bowman On BEST ANSWER

execute is a final method, and Mockito can't mock final methods (as mentioned in the section on spying and also the FAQ).

Why? Because you can't override final methods under normal circumstances, Java takes a shortcut and compiles calls (to final methods) directly to the implementations instead of looking them up in Java's equivalent of a virtual method table. This means that Mockito is never involved in final method calls, and therefore can't intercept behavior or even receive stubbing/verification calls.

Can you switch to mocking and using a raw HttpClient instead? You can mock any method of an interface without worrying about visibility or final method problems.