Search code examples
javaunit-testingmockitovert.xasynccallback

Mocking a Vertx.io async handler


when I was sync I wrote unit tests mocking the persistence part and check the caller's behavior. Here is an example about what I usually did:

@Mock
private OfferPersistenceServiceImpl persistenceService;
@Inject
@InjectMocks
private OfferServiceImpl offerService;
...
@Test
public void createInvalidOffer() {
  offer = new Offer(null, null, null, null, null, 4, 200D, 90D);
  String expectedMessage = Offer.class.getName() + " is not valid: " + offer.toString();
  Mockito.when(persistenceService.create(offer)).thenThrow(new IllegalArgumentException(expectedMessage));
  Response response = offerService.create(offer);
  Mockito.verify(persistenceService, Mockito.times(1)).create(offer);
  Assert.assertEquals(INVALID_INPUT, response.getStatus());
  String actualMessage = response.getEntity().toString();
  Assert.assertEquals(expectedMessage, actualMessage);
}

But now I fell in love with Vertx.io (to which I am pretty new) and I want to be async. Nice. But Vertx has handlers, so the new persistence component to mock looks like this:

...
mongoClient.insert(COLLECTION, offer, h-> {
  ...
});

So I am guessing how to mock handler h to tests class who's using that mongoClient or even if it is the right way to test with Vertx.io. I am using vertx.io 3.5.0, junit 4.12 and mockito 2.13.0. Thanks.

Update I tried to follow tsegimond suggestion but I can't get how Mockito's Answer and ArgumentCaptor can help me. Here is what I tried so far. Using ArgumentCaptor:

JsonObject offer = Mockito.mock(JsonObject.class);
Mockito.when(msg.body()).thenReturn(offer);         
Mockito.doNothing().when(offerMongo).validate(offer);
RuntimeException rex = new RuntimeException("some message");
...
ArgumentCaptor<Handler<AsyncResult<String>>> handlerCaptor =
ArgumentCaptor.forClass(Handler.class);
ArgumentCaptor<AsyncResult<String>> asyncResultCaptor =
ArgumentCaptor.forClass(AsyncResult.class);
offerMongo.create(msg);
Mockito.verify(mongoClient,
Mockito.times(1)).insert(Mockito.anyString(), Mockito.any(), handlerCaptor.capture());
Mockito.verify(handlerCaptor.getValue(),
Mockito.times(1)).handle(asyncResultCaptor.capture());
Mockito.when(asyncResultCaptor.getValue().succeeded()).thenReturn(false);
Mockito.when(asyncResultCaptor.getValue().cause()).thenReturn(rex);
Assert.assertEquals(Json.encode(rex), msg.body().encode());

and using Answer:

ArgumentCaptor<AsyncResult<String>> handlerCaptor =
ArgumentCaptor.forClass(AsyncResult.class);
AsyncResult<String> result = Mockito.mock(AsyncResult.class);
Mockito.when(result.succeeded()).thenReturn(true);
Mockito.when(result.cause()).thenReturn(rex);
Mockito.doAnswer(new Answer<MongoClient>() {
  @Override
  public MongoClient answer(InvocationOnMock invocation) throws Throwable {
    ((Handler<AsyncResult<String>>)
    invocation.getArguments()[2]).handle(handlerCaptor.capture());
        return null;
      }
    }).when(mongoClient).insert(Mockito.anyString(), Mockito.any(),
Mockito.any());
userMongo.create(msg);
Assert.assertEquals(Json.encode(rex), msg.body().encode());

And now I got confused. Is there a way to mock an AsyncResult to let it return false on succeed()?


Solution

  • Finally I got some times to investigate and I made it. Here is my solution.

    @RunWith(PowerMockRunner.class)
    @PowerMockRunnerDelegate(VertxUnitRunner.class)
    @PrepareForTest({ MongoClient.class })
    public class PersistenceTest {
    
    private MongoClient mongo;
    private Vertx vertx;
    
    @Before
    public void initSingleTest(TestContext ctx) throws Exception {
      vertx = Vertx.vertx();
      mongo = Mockito.mock(MongoClient.class);
      PowerMockito.mockStatic(MongoClient.class);
      PowerMockito.when(MongoClient.createShared(Mockito.any(), Mockito.any())).thenReturn(mongo);
      vertx.deployVerticle(Persistence.class, new DeploymentOptions(), ctx.asyncAssertSuccess());
    }
    
    @SuppressWarnings("unchecked")
    @Test
    public void loadSomeDocs(TestContext ctx) {
      Doc expected = new Doc();
      expected.setName("report");
      expected.setPreview("loremipsum");
      Message<JsonObject> msg = Mockito.mock(Message.class);
      Mockito.when(msg.body()).thenReturn(JsonObject.mapFrom(expected));
      JsonObject result = new JsonObject().put("name", "report").put("preview", "loremipsum");
      AsyncResult<JsonObject> asyncResult = Mockito.mock(AsyncResult.class);
      Mockito.when(asyncResult.succeeded()).thenReturn(true);
      Mockito.when(asyncResult.result()).thenReturn(result);
      Mockito.doAnswer(new Answer<AsyncResult<JsonObject>>() {
        @Override
        public AsyncResult<JsonObject> answer(InvocationOnMock arg0) throws Throwable {
        ((Handler<AsyncResult<JsonObject>>) arg0.getArgument(3)).handle(asyncResult);
        return null;
        }
      }).when(mongo).findOne(Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any());
      Async async = ctx.async();
      vertx.eventBus().send("persistence", new JsonObject(), msgh -> {
        if (msgh.failed()) {
        System.out.println(msgh.cause().getMessage());
        }
        ctx.assertTrue(msgh.succeeded());
        ctx.assertEquals(expected, Json.decodeValue(msgh.result().body().toString(), Doc.class));
        async.complete();
      });
      async.await();
      }
    }
    

    Use Powemockito to mock the MongoClient.createShared static method, so you'll have your mock when verticle starts. Mocking async handler is a bit of code to write. As you can see mocking start at Message<JsonObject> msg = Mockito.mock(Message.class); and ends at Mockito.doAnswer(new Answer.... In the Answer's method pick the handler param and force it to handle your async result then you're done.