使用Apache HTTP Client模拟中断的连接(用于测试目的)

时间:2014-04-26 15:15:21

标签: testing apache-httpclient-4.x

我正在编写一个使用Apache HttpClient与外部REST-API通信的应用程序。

它执行的任务包括一些冗长的PUT操作,如果有人失败,我的应用程序应自动重试继续操作,因为有人通过网络电缆等绊倒。

我想为这种行为编写一个测试,我的想法是打开一个模拟连接,它会占用X个字节,然后抛出一个IOException。为了能够这样做,我将HTTPClient注入我的系统,因此我可以将预先配置的HTTPClient注入我的系统进行测试,这将显示所需的行为。 HTTPClient有很多抽象,工厂等,我相信这可能是可能的,但我倾向于完全迷失在所有深处。

1 个答案:

答案 0 :(得分:2)

我成功地实际提交了这个问题,直到我最终设法让它自己工作(花了我足够长的时间): - )。

在这个库中,很多东西都被抽象出来,就像一切都可以配置一样。套接字除外。唯一似乎在apache http客户端中缺少任何抽象的东西。我花了很长时间才尝试让lib工作在流上,而不是套接字,直到我最终放弃并实现了我自己的"套接字"。

但后来很简单:在HttpClientBuilder中可以设置connectionManager来实例化套接字并连接它们。您可以实现自己的连接管理器并返回带有覆盖getOutputStream / getInputStream方法的套接字,然后您可以使用您创建的那些流。不要忘记也覆盖连接方法,因为我们不想在这里创建任何网络活动。

以下是一个示例TestNG-Test,它演示了行为,并且您可以在其上构建。

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.net.Socket;

import org.apache.http.HttpHost;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.conn.socket.ConnectionSocketFactory;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.conn.BasicHttpClientConnectionManager;
import org.apache.http.protocol.HttpContext;
import org.testng.annotations.Test;

public class InterruptedConnectionTest {

    private static final InputStream EMPTY_INPUT_STREAM = new InputStream() {
        @Override
        public int read() throws IOException {
            return -1;
        }
    };

    private static final ConnectionSocketFactory FAILURE_SOCKET_FACTORY = new ConnectionSocketFactory() {

        @Override
        public Socket createSocket(HttpContext context) throws IOException {
            final InputStream in = EMPTY_INPUT_STREAM;
            final OutputStream out = new FailureOutputStream(10);
            return new Socket() {
                @Override
                public InputStream getInputStream() throws IOException {
                    return in;
                }

                @Override
                public OutputStream getOutputStream() throws IOException {
                    return out;
                }
            };
        }

        @Override
        public Socket connectSocket(int connectTimeout, Socket sock,
                HttpHost host, InetSocketAddress remoteAddress,
                InetSocketAddress localAddress, HttpContext context)
                throws IOException {
            return sock; // do nothing
        }
    };

    @Test(expectedExceptions = MockIOException.class)
    public void executingRequest_throwsException() throws Exception {
        HttpClient httpClient = HttpClientBuilder
                .create()
                .setConnectionManager(new BasicHttpClientConnectionManager(RegistryBuilder.<ConnectionSocketFactory>create()
                        .register("http", FAILURE_SOCKET_FACTORY)
                        .build()))
                .build();
        httpClient.execute(new HttpGet("http://localhost/some/path"));
    }

    private static class FailureOutputStream extends OutputStream {
        private int bytesRead = 0;
        private final int failByte;

        private FailureOutputStream(int failByte) {
            super();
            this.failByte = failByte;
        }

        @Override
        public void write(int b) throws IOException {
            ++bytesRead;
            if (bytesRead >= failByte) {
                throw new MockIOException("Mock error after having having read <" + bytesRead + "> bytes");
            }
        }
    }

    private static class MockIOException extends IOException {
        public MockIOException(String message) {
            super(message);
        }
    }
}