5

The code I'm testing works correctly, logs are right.


Tests in error: ConnectorTest: Unable to initialize @Spy annotated field 'entity'.


  • Why can't I use verify on the http entity?
  • Is there a better way to test it? Should I spy on the logs otherwise?

CLASS TO BE TESTED:

public class Connector {

    private static final String hostname = "localhost";
    private int varnishPort = 8000;

    private int connectionTimeout = 5000; //millis
    private int requestTimeout = 5000;
    private int socketTimeout = 5000;

    private final HttpHost host;
    private HttpClient httpClient;
    private HttpEntity entity;
    private HttpResponse response;

    public Connector(){

        host = new HttpHost(this.hostname, this.varnishPort);

        RequestConfig.Builder requestBuilder = RequestConfig.custom();
        requestBuilder = requestBuilder.setConnectTimeout(connectionTimeout);
        requestBuilder = requestBuilder.setConnectionRequestTimeout(requestTimeout);
        requestBuilder = requestBuilder.setSocketTimeout(socketTimeout);

        HttpClientBuilder builder = HttpClientBuilder.create();     
        builder.setDefaultRequestConfig(requestBuilder.build());
        httpClient = builder.build();
    }


    public void invalidateVarnishCache( String level, String idDb ) {

        try{
            String xPurgeRegex = level+"/"+idDb+"$";
            Header header = new BasicHeader( "X-Purge-Regex", xPurgeRegex );
            BasicHttpRequest purgeRequest = new BasicHttpRequest("PURGE", "/" );
            purgeRequest.setHeader(header);

            response = httpClient.execute(host, purgeRequest);

            entity = response.getEntity();

            int statusCode = response.getStatusLine().getStatusCode();

            if( statusCode >= 300 ){

                int respLength = entity.getContent().available();
                byte[] errorResp = new byte[ respLength ];
                entity.getContent().read(errorResp, 0, respLength);
                // log error
            }else{
                // log success
            }

            EntityUtils.consume(entity);

        }catch(Exception e){
            // log exception
        }
    }

}

MY TEST:

@RunWith(MockitoJUnitRunner.class)
public class ConnectorTest {

    @Mock
    private HttpClient httpClient;

    // @Spy
    // private HttpEntity entity;

    @InjectMocks
    private Connector varnishPurger = new Connector();


    @Test
    public void purgeFail() throws Exception{

        HttpResponse response500 = new BasicHttpResponse( new ProtocolVersion( "HTTP/1.1", 0, 0), 500, "NO!_TEST" );
        HttpEntity entity500 = new StringEntity("BAD entity. [test]");
        response500.setEntity(entity500);

        doReturn( response500 )
        .when( httpClient ).execute( isA(HttpHost.class), isA(BasicHttpRequest.class) );

        varnishPurger.invalidateVarnishCache("level_test_500", "id_test_500");

        // verify( entity, times( 1 ) ).getContent().available();  // HOW DO I MAKE THIS WORK?
    }

    ...

}
Gabe
  • 5,997
  • 5
  • 46
  • 92
  • 2
    HttpEntity is an probably interface, so spying on it would be pointless (as an interface doesn't actually do anything). Mock it instead, if that's the case. See http://docs.mockito.googlecode.com/hg/1.9.5/org/mockito/Spy.html for things Mockito cannot spy on automatically (you could put an implementing class there, for example via `private HttpEntity entity = new MyEntityImpl();`, that should work) – Florian Schaetz Feb 18 '16 at 13:04
  • Please avoid google code documentation and use the doc links from github ;) => http://site.mockito.org/mockito/docs/1.10.19/org/mockito/Mockito.html – bric3 Feb 18 '16 at 13:21

2 Answers2

7

HttpEntity is an interface, it cannot be spied, only mocked. So just change the annotation with @Mock, or another option is to declare an initialised instance :

@Spy HttpEntity entity = new BasicHttpEntity();

Anyway the exception message is rather clear on why this happens :

org.mockito.exceptions.base.MockitoException: Unable to initialize @Spy annotated field 'entity'.
Type 'HttpEntity' is an interface and it cannot be spied on.

    at org.mockito.internal.runners.JUnit45AndHigherRunnerImpl$1.withBefores(JUnit45AndHigherRunnerImpl.java:27)
    at org.junit.runners.BlockJUnit4ClassRunner.methodBlock(BlockJUnit4ClassRunner.java:276)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
    at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
    at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
    at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
    at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
    at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
    at org.mockito.internal.runners.JUnit45AndHigherRunnerImpl.run(JUnit45AndHigherRunnerImpl.java:37)
    at org.mockito.runners.MockitoJUnitRunner.run(MockitoJUnitRunner.java:62)
    at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
    at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:69)
    at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:234)
    at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:74)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:497)
    at com.intellij.rt.execution.application.AppMain.main(AppMain.java:144)
Caused by: org.mockito.exceptions.base.MockitoException: Type 'HttpEntity' is an interface and it cannot be spied on.
    ... 21 more

This behaviour is aligned with Mockito.spy(Object), this method cannot be invoked if there's no instance. However the recent Mockito.spy(Class) does not complain about that. This may be a functionality that wasn't ported to the annotation subsystem.

Nonetheless it is semantically wrong to spy on an interface as it doesn't have behaviour.

bric3
  • 40,072
  • 9
  • 91
  • 111
  • Right. Maven just gave me the short version. My intent anyway is to spy on the real HttpEntity that I'm testing in order to mock just the real HttpClient and HttpResponse, is it possible somehow? – Gabe Feb 18 '16 at 14:03
  • @GaSacchi I'm not following exactly the test intent, but why not setting up the http response mock to return a spied `BasicHttpEntity` ? – bric3 Feb 18 '16 at 14:42
0

I solved in this way:

class MockHttpEntity implements HttpEntity{

    String msg = "Test_";
    InputStream inps;
    int count = 0;

    public MockHttpEntity(String msg){
        this.msg += msg;
        inps = IOUtils.toInputStream(this.msg);
    }

    @Override
    public InputStream getContent(){
        System.out.println("\n"+(++count)+") Mocked getContent() called.\n");
        return inps;
    }

    public int times(){
        return count;
    }

    @Override public void       consumeContent(){  }
    @Override public Header     getContentEncoding(){ return null; }
    @Override public long       getContentLength(){ return 0; }
    @Override public Header     getContentType(){ return null;}
    @Override public boolean    isChunked(){ return false; }
    @Override public boolean    isRepeatable(){ return false; }
    @Override public boolean    isStreaming(){ return false; }
    @Override public void       writeTo(OutputStream outstream){  }
}


@RunWith(MockitoJUnitRunner.class)
public class ConnectorTest {    

    @Mock
    private HttpClient httpClient;

    @InjectMocks
    private Connector varnishPurger = new Connector();


    @Test
    public void purgeFailureResponse() throws Exception{

        MockHttpEntity mockedHttpEntity = new MockHttpEntity("bad entity");

        HttpResponse response500 = new BasicHttpResponse( new ProtocolVersion( "HTTP/1.1", 0, 0), 500, "NO!_TEST" );
        response500.setEntity( mockedHttpEntity );

        doReturn( response500 )
        .when( httpClient ).execute( isA(HttpHost.class), isA(BasicHttpRequest.class) );

        varnishPurger.invalidateVarnishCache("level_test_no", "id_test_no");

        Assert.assertTrue( mockedHttpEntity.times() == 2 );
    }
    ...
}
Gabe
  • 5,997
  • 5
  • 46
  • 92