Mockito - 无法在 HttpEntity 上初始化 Spy

Mockito - Unable to initialize Spy on HttpEntity

我正在测试的代码工作正常,日志是正确的。


错误测试: ConnectorTest:无法初始化 @Spy 注释字段 'entity'。


CLASS待测试:

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

}

我的测试:

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

    ...

}

HttpEntity 是一个接口,它不能被窥探,只能被嘲笑。所以只需用 @Mock 更改注释,或者另一种选择是声明一个初始化实例:

@Spy HttpEntity entity = new BasicHttpEntity();

无论如何,异常消息非常清楚地说明了发生这种情况的原因:

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.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.run(ParentRunner.java:290)
    at org.junit.runners.ParentRunner.schedule(ParentRunner.java:71)
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
    at org.junit.runners.ParentRunner.access[=11=]0(ParentRunner.java:58)
    at org.junit.runners.ParentRunner.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

此行为与Mockito.spy(Object)一致,如果没有实例则无法调用此方法。 然而,最近的 Mockito.spy(Class) 并没有抱怨这一点。这可能是未移植到注释子系统的功能。

尽管如此,监视接口在语义上是错误的,因为它没有行为。

我是这样解决的:

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 );
    }
    ...
}