bthitman
bthitman

Reputation: 71

How can i unit test a method that contains vertx().eventBus().send()?

Frustrating. Everywhere i look, i see samples of testing async Vertx code, but nothing that comes close to what i am trying to test.

The method under test sends a message to the event bus. I want to verify that what happens in the eventBus().send() response handler is happening.

Sooooooo many examples i see have the eventBus().send() method in the TEST ITSELF (thus, testing the other end of the event bus - the consumer) I want to test the response handler in the .send()

I have tried Async in the test. Tried ArgumentCaptor. Tried Thread.sleep(). Tried doAnswer(). Nothing seems to get the test to (a) wait for the async eventBus().send() call in the method under test to finish and (b) able to verify() that there was an interaction (i think this might have to do with the different between the Vertx.TestContext and the JUnit.Runner Context..)

Code: Method under test:

public void sendToEventBusAddress(RoutingContext context, String requestId, String userId) {
    List<String> stuff = split(context.request().getParam("stuffToSplit"));
    JsonObject eventBusMessage = new JsonObject()
            .put("requestId", requestId)
            .put("stuffList", new JsonArray(stuff))
            .put("userId", userId);
    LOGGER.info("Putting message: {} onto the EventBus at address: {}", eventBusMessage.encodePrettily(), EventBusEndpointEnum.STUFF_ACCESS.getValue());
    context.vertx().eventBus().send(EventBusEndpointEnum.STUFF_ACCESS.getValue(), eventBusMessage, new DeliveryOptions().setSendTimeout(timeout), async -> {
        if (async.succeeded()) {
            LOGGER.info("EventBus Response: {}", async.result().body().toString());
            context.response().setStatusCode(HttpStatus.SC_OK);
            context.response().headers().set(HttpHeaders.CONTENT_TYPE, MediaType.TEXT_PLAIN);
            context.response().end(async.result().body().toString());
        } else {
            LOGGER.error(errorMessage);
            context.response().setStatusCode(HttpStatus.SC_BAD_REQUEST);
            context.response().end(errorMessage);
        }
    });
}

Simplified (non-working) Test case and class:

@RunWith(VertxUnitRunner.class)
public class MyBrokenTest {
    @Mock private RoutingContext routingContext;
    @Mock private HttpServerRequest contextRequest;
    @Mock private HttpServerResponse contextResponse;
    @Mock private MultiMap responseHeaders;

    @Rule public RunTestOnContext rule = new RunTestOnContext();

    @Before
    public void setUp(TestContext context) {
        MockitoAnnotations.initMocks(this);
    }

    @Test
    public void testOne(TestContext context) {
        when(routingContext.vertx()).thenReturn(rule.vertx());
        when(routingContext.request()).thenReturn(contextRequest);
        when(contextRequest.getParam("stuffToSplit")).thenReturn("04MA");
        when(routingContext.response()).thenReturn(contextResponse);
        when(contextResponse.headers()).thenReturn(responseHeaders);

        rule.vertx().eventBus().consumer(EventBusEndpointEnum.STUFF_ACCESS.getValue(), res -> {
            res.reply("yo");
        });

        ClassUnderTest cut= new ClassUnderTest(180000);
        cut.sendToEventBusAddress(routingContext, "testRequestId", "UnitTestId");
        verify(contextResponse).setStatusCode(200);
    }
}

I know that the test in its current form won't work, because the method under test returns as soon as the eventBus().send() method is called inside the method, and therefore, the verify fails with 'no interactions'.

What i can't figure out, is how to verify it properly given the async nature of Vertx!

Thanks

Upvotes: 0

Views: 2987

Answers (1)

Eduard Dubilyer
Eduard Dubilyer

Reputation: 1013

I did it so:

  1. At @BeforeAll annotated method I deploy only the sending verticle.

  2. At @BeforeEach - I create a consumer for the given message and store message(s) to variable/collection

    Something like:

    receivedMessage = new Message[1];
       eventBus.consumer("DB",
            message -> {
                message.reply("OK");
                receivedMessage[0] = message;
            });
    context.completeNow();
    
  3. In test I validate stored value(s):

    client.get(8080, "localhost", "/user/" + id)
        .as(BodyCodec.string())
        .send(context.succeeding((response -> context.verify(() -> {
            Assertions.assertEquals(expectedMessage, receivedMessage[0].body());
            context.completeNow();
    }))));
    

Upvotes: 2

Related Questions