Dillon Krompetz created HTTPCLIENT-2216:
-------------------------------------------
Summary: ClosableHttpAsyncClient interface and testing
Key: HTTPCLIENT-2216
URL: https://issues.apache.org/jira/browse/HTTPCLIENT-2216
Project: HttpComponents HttpClient
Issue Type: Improvement
Components: HttpClient (async)
Affects Versions: 5.0
Reporter: Dillon Krompetz
The current CloseableHttpAsyncClient implementation currently has a problem in
its interface and intended usage such that testing becomes more difficult than
it needs to be.
### Class in question
{code:java}
public abstract class CloseableHttpAsyncClient implements HttpAsyncClient,
ModalCloseable {code}
### Methods in question
{code:java}
public abstract void start(); {code}
- defined in CloseableHttpAsyncClient class
{code:java}
<T> Future<T> execute(
AsyncRequestProducer requestProducer,
AsyncResponseConsumer<T> responseConsumer,
HandlerFactory<AsyncPushConsumer> pushHandlerFactory,
HttpContext context,
FutureCallback<T> callback
); {code}
- defined in HttpAsyncClient interface
{code:java}
void close(CloseMode closeMode); {code}
- defined in ModalCloseable interface
### How is this an issue?
When a developer goes to make use of the Http5 async client they may follow the
guides provided:
https://hc.apache.org/httpcomponents-client-5.1.x/examples-async.html and see
that they need access to the above three methods.
They may introduce an adapter around this interface to take thier business
logic and convert it into an Http request compatable with the client.
{code:java}
class MyHttp5AsyncClient {
private CloseableHttpAsyncClient client;
public MyHttp5AsyncClient(CloseableHttpAsyncClient httpAsyncClient) {
this.client = httpAsyncClient;
}
public start() {
this.client.start();
}
public stop() {
this.client.close();
}
public execute(MyRequest request) {
AsyncRequestProducer requestProducer = toRequestProducer(request);
Future<SimpelHttpResponse> result = client.execute(
requestProdcuer,
SimpleResponseConsumer.create(),
null,
null,
null
);
return result.get();
}
} {code}
Note that in the CloseableHttpAsyncClient implementation that the execute
method is final:
{code:java}
public final <T> Future<T> execute(
final HttpHost target,
final AsyncRequestProducer requestProducer,
final AsyncResponseConsumer<T> responseConsumer,
final HandlerFactory<AsyncPushConsumer> pushHandlerFactory,
final HttpContext context,
final FutureCallback<T> callback) {
Args.notNull(requestProducer, "Request producer");
Args.notNull(responseConsumer, "Response consumer");
return doExecute(target, requestProducer, responseConsumer,
pushHandlerFactory, context, callback);
} {code}
### Testing
Popular testing patterns include injecting the dependency into the class to
verify that methods were called with certain arguments or to mock the result of
calling a method to control the code flow in isolation. A very popular Java
mocking and verifying library might be Mockito where a developer may write
something like:
{code:java}
class MyHttp5AsyncClientTest {
@Mock
private CloseableHttpAsyncClient client;
private MyHttp5AsyncClient toTest;
@BeforeEach
public void setup() {
toTest = new MyHttp5AsyncClient(client);
}
@Test
public void execute_whenCalledWithSomeInput_thenWillDoTheThing() {
when(client.execute(
any(AsyncRequestProducer.class),
any(AsyncResponseConsumer.class),
nullable(HandlerFactory.class),
nullable(HttpContext.class),
nullable(FutureCallback.class)
)).thenReturn(
CompletableFuture.completedFuture(null)
);
var result = toTest.execute(null);
// ...
// some expectations on the result that are interesting.
}
} {code}
This test will fail because the hard implementation of CloseableHttpAsyncClient
execute is final and when mocking it will call the actual method with the
result of calls to Mockito's any() which returns nulls. This will then be
caught by the guard statements in the actual method call to execute.
### How can this be made better?
How I got around the issue:
- Inserted a wrapper around CloseableHttpAsyncClient that had the public
methods I needed to use that could be mocked.
How might this be better resolved:
1. Remove final modifier from the CloseableHttpAsyncClient methods that are
likely to be mocked.
- There is likely a reason these methods were intended to not be open for
modification :)
2. Insert an interface that CloseableHttpAsyncClient implements that captures
its intended functionality.
- This new interface would include the public methods of
CloseableHttpAsyncClient and extend the interfaces for HttpAsyncClient and
ModalCloseable
--
This message was sent by Atlassian Jira
(v8.20.7#820007)
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]