Pregunta

I am writing an Application which communicates with an external REST-API using Apache HttpClient.

The tasks it performs include some lengthy PUT-Operations and my application should automatically retry to continue the operation if it has failed, because someone tripped over the network cable or the like.

I'd like to write a test for this behaviour and my idea is to open a mock connection which will consume X bytes and then throw an IOException. To be able to do so I'll inject the HTTPClient into my system and thus I can inject a preconfigured HTTPClient into my system for testing which will show the desired behaviour. The HTTPClient has so many abstractions, factories and so on that I'll believe that this is probably possible, but I tend to get totally lost in all the depths.

¿Fue útil?

Solución

I sucessfully put of to actually submit that question until I eventually managed to get it working myself (took me long enough) :-).

Pretty much EYERYTHING is somehow abstracted away in this library and like EVERYTHING can be configured. Except Sockets. The only thing which seems to lack any abstraction in apache http client. It took me forever to try to get the lib work just on streams and not on Sockets until I finally gave up and implemented my own "sockets".

But then it was quite simple: In the HttpClientBuilder a connectionManager can be set which instantiates sockets and connects them. You can implement your own connection manager and return a socket with an overriden getOutputStream/getInputStream method and then you can work with those streams that you created. Don't forget to also override the connect method, because we don't want to create any network activity here.

Here is an example TestNG-Test which demonstrates the behavior and on which you can surely built upon.

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);
        }
    }
}
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top