Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Mockito - Unable to initialize Spy on HttpEntity

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?
    }

    ...

}
like image 897
Gabe Avatar asked Mar 13 '23 06:03

Gabe


1 Answers

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.

like image 146
Brice Avatar answered Mar 28 '23 21:03

Brice