Hot questions for Using Mockito in vert.x

Question:

I have a method that makes a call to external endpoint using io.vertx.ext.web.client.WebClient . I am not able to test the handler method of it. This is the method that needs to be tested:

public void freshdeskPostRequest(CompletableFuture<ResponseObject> completableFuture, String url, JsonObject jsonObject, String action) {
        webClient.postAbs(url)
                .putHeader("Content-type", "application/json")
                .putHeader(Constants.AUTHORIZATION, freshdeskAuthHandler)
                .timeout(fresdeskTimeout)
                .sendJsonObject(jsonObject, httpResponseAsyncResult -> {
                    getFreshdeskResponse(completableFuture, action, httpResponseAsyncResult);
                });
    }

The method in it getFreshdeskResponse needs to be tested by making a mock call to the url. But the method is called in handler so I am not sure how to mock the call and execute the handler. I checked several answers on the forum as well went through the docs but none of them helped. Please help. I am using Junit, Mockito as testing frameworks. Please help.


Answer:

You could use ArgumentCaptor of the mockito library to capture the lambda expression and trigger the lambda manually. Ex :

@Captor
ArgumentCaptor<SomeHandlerType> captor;  // create ArgumentCaptor for handler

SomeHandlerType is the type of handler. Then call the sendJsonObject() with captor.capture() like so -

mockedRequest.sendJsonObject(captor.capture()); // capture the argument
SomeHandlerType handler = captor.getValue(); // get the handler lambda
handler.handle(dummyResponse);  // trigger the handler manually

Question:

I have a method which creates a HttpClientRequest instance and associates a Handler to it.

public void sendRequest(String requestId, File file, Message<A> message) {

        final HttpClientRequest request = getHttpClientRequest();
        request.putHeader(HttpHeaders.CONTENT_TYPE.toString(), FORM_DATA);
        request.putHeader(HttpHeaders.ACCEPT.toString(), APPNEXUS_JSON_HEADER);
        request.putHeader(HttpHeaders.CONTENT_TRANSFER_ENCODING.toString(), "binary");
        final Buffer buffer = this.getBody(file.getAbsolutePath());
        request.putHeader(HttpHeaders.CONTENT_LENGTH.toString(), String.valueOf(buffer.length()));
        request.handler(httpClientResponse -> {
          switch (httpClientResponse.statusCode()) {
            case Status.SC_OK:
              httpClientResponse.bodyHandler(body -> {
                 // Do something
              });
              break;
            case Status.TOO_MANY_REQUESTS:
              // Do  something
              break;
            default:
              // Do something
          }
        });}

The client request is to a 3rd party service. How should I write the unit test to invoke different clauses of the handler? I am using Mockito for mocking tasks.

Test that I have written so far,

public void testSomething (TestContext testContext) { 
      final Async async = testContext.async();
      Mockito.when(httpClientRequest.exceptionHandler(Mockito.any())).thenReturn(httpClientRequest);
      Mockito.when(httpClientRequest.putHeader(Mockito.anyString(), Mockito.anyString())).thenReturn(httpClientRequest);
      Mockito.doAnswer(invocation -> {
        return httpClientResponse;      
      }).when(httpClientRequest).end(Mockito.any(Buffer.class)); 
      Mockito.when(routingContext.response()).thenReturn(httpServerResponse);
      Mockito.when(routingContext.statusCode()).thenReturn(200);
      Mockito.when(routingContext.getBody()).thenReturn(buffer);   
      JsonObject jsonObject = Mockito.mock(JsonObject.class);
      Mockito.when(buffer.toJsonObject()).thenReturn(jsonObject);                          Mockito.when(jsonObject.mapTo(Mockito.any())).thenReturn(appnexusBulkSyncResponse);
      Mockito.when(file.getAbsolutePath()).thenReturn("testpath");
      String requestId = "req-1";
      JsonObject uploadRequest = new JsonObject();
      uploadRequest.put("requestId", requestId);
      vertx.eventBus().consumer("test-bus", (Message<A> message) -> {
         syncClient.sendRequest(requestId, file, message);
      });
      vertx.eventBus().send("test-bus", uploadRequest, event -> {
        async.complete();
      });
      async.await(TIMEOUT);
 }

You may assume all variables are mocked as required. I would like the test to invoke the request.handler in sendRequest method. Validated that the entire flow executes up to request.end.


Answer:

I would suggest to mock the responses of that 3rd party service with Wiremock:

@RunWith(VertxUnitRunner.class)
public class MyTestClass {

    private static WireMockServer wiremock;
    private Vertx vertx;

    @BeforeClass
    public static void init() {
        wiremock = new WireMockServer(host, port); //configure host and port
        wiremock.start();
    }

    @AfterClass
    public static void cleanup() {
        wiremock.stop();
    }

    @Before
    public void setup(TestContext ctx){
        //init vertx
        Async async = testContext.async();
        vertx = Vertx.vertx();
        ...
        async.complete();
    }

    @Test
    public void mytest(TestContext testContext){
        Async async = testContext.async();
        stubFor(get(urlPathMatching("/.*"))
          .willReturn(aResponse()
          .withStatus(200)
          .withHeader("Content-Type", "application/json")
          .withBody("{}")));

        HttpClient client = vertx.createHttpClient(...)
        client.getNow("/some-uri", response -> {
            //check response
            async.complete();
        });
    }

}

Question:

The io.vertx.reactivex.core.eventbus.EventBus.rxSend() method has the following signature:

public <T> Single<Message<T>> rxSend(String address,
                                     Object message,
                                     DeliveryOptions options)

What is the correct way to mock this so that it returns a Single containing a real object? The issue is that the Message class has no constructor apart from one which which takes another Message object. So the following will compile:

Mockito.when(eventBus.rxSend(Mockito.isA(String.class), 
   Mockito.isA(JsonObject.class), 
   Mockito.isA(DeliveryOptions.class))).thenReturn(Single.just(new Message<Object>(null)));

but of course Single.just(new Message<Object>(null))does not contain a real object which can then be passed on to test the next handler in the verticle.

Thanks


Answer:

like i mentioned in my comment, i don't have an answer to your immediate question, but i'd instead like to recommend a different approach to getting the results you're looking for.

mocking types that you don't own is generally discouraged for a variety of reasons. the two that resonate most with me (as i've fallen victim) are:

  • if the real implementation of the mocked dependency changes, the mock's behavior will not automatically reveal any forward-breaking changes.
  • the more mocks a test introduces, the more cognitive load the test carries. and some tests require a lot of mocks in order to work.

there are lots of articles on the topic with more detailed viewpoints and opinions. if you're interested, refer to the Mockito wiki, or just Google around.

given all that, rather than mocking EventBus, why not use an actual instance and receive real reply Messages composed by the framework? sure, strictly speaking this becomes more of an integration test than a unit test, but is closer to the type of testing you want.

here's an example snippet from a test i wrote in an existing project with some added comments. (the code refers to some non-standard types with an -"Ext" suffix, but they aren't salient to the approach).

private EventBus eventBus;

@Before
public setUp(@NotNull TestContext context) {
    eventBus = Vertx.vertx().eventBus()
}

@Test
public void ping_pong_reply_test(@NotNull TestContext context) {
    final Async async = context.async();

    // the following is a MessageConsumer registered 
    // with the EventBus for this specific test. 
    // the reference is retained so that it can be 
    // "unregistered()" upon completion of this test
    // so as not to affect other tests. 

    final MessageConsumer<JsonObject> consumer = eventBus.consumer(Ping.class.getName(), message -> {
        // here is where you would otherwise place
        // your mock Message generation.

        MessageExt.replyAsJsonObject(message, new Pong());
    });

    final Ping message = new Ping();
    final DeliveryOptions options = null;

    // the following uses an un-mocked EventBus to 
    // send an event and receive a real Message reply.
    // created by the consumer above.

    EventBusExt.rxSendJsonObject(eventBus, message, options).subscribe(
            result -> 
                // result.body() is JSON that conforms to 
                // the Pong type

                consumer.unregister();

                async.complete();
            },
            error -> {
                context.fail(error);
            }
    );
}

i hope this at least inspires some new thinking around your problem.