我正在编写一个使用Apache HttpClient与外部REST-API通信的应用程序。
它执行的任务包括一些冗长的PUT操作,如果有人失败,我的应用程序应自动重试继续操作,因为有人通过网络电缆等绊倒。
我想为这种行为编写一个测试,我的想法是打开一个模拟连接,它会占用X个字节,然后抛出一个IOException。为了能够这样做,我将HTTPClient注入我的系统,因此我可以将预先配置的HTTPClient注入我的系统进行测试,这将显示所需的行为。 HTTPClient有很多抽象,工厂等,我相信这可能是可能的,但我倾向于完全迷失在所有深处。
答案 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);
}
}
}