13

I'm trying to test my code which uses the new Java 11 java.net.http.HttpClient.

In my production code I have something like this:

HttpClient httpClient = ... (gets injected)

HttpRequest request = HttpRequest.newBuilder().uri(URI.create("http://localhost:1234"))
        .POST(HttpRequest.BodyPublishers.ofByteArray("example".getBytes()))
        .build();

return httpClient.send(request, HttpResponse.BodyHandlers.ofByteArray());

And in my test I mock the HttpClient and so get the java.net.http.HttpRequest. How do I get/test its request body (= my "example")? I can call request.bodyPublisher() to get a HttpRequest.BodyPublisher, but then I'm stuck.

  • I've tried to cast it to jdk.internal.net.http.RequestPublishers.ByteArrayPublisher (which it actually is), but it won't compile because the corresponding package is not exported by the module.
  • I've checked the available methods in the HttpRequest.BodyPublisher-interface (.contentLength(), .subscribe(subscriber)) but I guess it's not possible with them.
  • I've tried to just create a new BodyPublisher and compare them using .equals(), but there is no real implementation of it and so the comparison was always false.
Naman
  • 27,789
  • 26
  • 218
  • 353
Itchy
  • 2,263
  • 28
  • 41
  • 1
    The request `body` is an input to the `HttpRequest` you're trying to make. Which itself should be ideally something that you a user should provide, not the other way round, that the `HttpRequest` provides you the body. Is there something else that I am missing in your question? – Naman Dec 15 '19 at 18:12
  • True, in my production code I put the `body` into the `HttpRequest` (see first block of code). But in my **test**, I'd like to verify that the correct `body` has been set in the request - thus getting it out again. – Itchy Dec 15 '19 at 18:21

2 Answers2

6

If you are interested in how body will look like in handler you can know it with help of HttpRequest.BodyPublisher Subscriber. We call subscription.request in order to receive all body items and collect them.

Our custrom subscriber:

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Flow;

public class FlowSubscriber<T> implements Flow.Subscriber<T> {
    private final CountDownLatch latch = new CountDownLatch(1);
    private List<T> bodyItems = new ArrayList<>();

    public List<T> getBodyItems() {
        try {
            this.latch.await();
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        return bodyItems;
    }

    @Override
    public void onSubscribe(Flow.Subscription subscription) {
        //Retrieve all parts
        subscription.request(Long.MAX_VALUE);
    }

    @Override
    public void onNext(T item) {
        this.bodyItems.add(item);
    }

    @Override
    public void onError(Throwable throwable) {
        this.latch.countDown();
    }

    @Override
    public void onComplete() {
        this.latch.countDown();
    }
}

Usage in the test:

@Test
public void test() {
    byte[] expected = "example".getBytes();

    HttpRequest.BodyPublisher bodyPublisher =
            HttpRequest.BodyPublishers.ofByteArray(expected);

    FlowSubscriber<ByteBuffer> flowSubscriber = new FlowSubscriber<>();
    bodyPublisher.subscribe(flowSubscriber);

    byte[] actual = flowSubscriber.getBodyItems().get(0).array();

    Assert.assertArrayEquals(expected, actual);
}
Yan
  • 318
  • 2
  • 10
  • 2
    Looks terribly complicated but seems to work. Thank you! – Itchy Dec 15 '19 at 20:15
  • 5
    Yes, I totally agree, but "as designed". There is interesting library for testing httpclient https://github.com/PGSSoft/HttpClientMock – Yan Dec 15 '19 at 20:20
  • 2
    The use of `CountDownLatch` here is curious: I would advise using a `CompletableFuture>` instead - as this doesn't force you back into a synchronous API and makes it possible to relay any exception received by `onError` as well. – daniel Dec 16 '19 at 14:01
1

If you're OK using a simple dependency, then see HttpClientMock.

For your example, you'd mock and verify your request like so:

// Given
httpClientMock
  .onPost("http://localhost:1234")
  .withBody(is("example"))
  .doReturn(200, "ok");

// When
/* call your function here */

// Then
httpClientMock
  .verify()
  .post("http://localhost:1234")
  .withBody(is("example"))
  .called(1);

I had reason to do this with an OpenAPI generated client that used the HttpClient of JDK 11, and spent a few hours trying to "do it the right way", but it only took a few minutes to get the HttpClientMock way going.

I detailed a lot more details on my experience in this gist: https://gist.github.com/booniepepper/6fe970e1de28f975887a5e7af8a2cd8b

Abra
  • 19,142
  • 7
  • 29
  • 41