How to write unit tests for event bus handlers?

3,694 views
Skip to first unread message

Psycho Punch

unread,
Sep 21, 2016, 12:33:35 AM9/21/16
to vert.x
I'm new to Vert.x and I'm trying to figure out how to do unit tests for event bus handlers. Most of the documentation I see regarding testing have examples using HTTP components for testing. However, I'm working on a very simple handler for events using the event bus, and I'm not really sure how to set up the tests for it.

I'm not sure what the community's general view of StackOverflow is, but I've posted a question about my problem in there earlier. Please check it out for more details. If you'd rather I repost the question in here, please let me know.

Julien Viet

unread,
Sep 21, 2016, 1:08:44 AM9/21/16
to ve...@googlegroups.com
I think it depends what you handler does and how it react to events.

You want usually to send event to trigger handler reaction and then check the handler reacted correctly.

On Sep 21, 2016, at 6:33 AM, Psycho Punch <rdg...@gmail.com> wrote:

I'm new to Vert.x and I'm trying to figure out how to do unit tests for event bus handlers. Most of the documentation I see regarding testing have examples using HTTP components for testing. However, I'm working on a very simple handler for events using the event bus, and I'm not really sure how to set up the tests for it.

I'm not sure what the community's general view of StackOverflow is, but I've posted a question about my problem in there earlier. Please check it out for more details. If you'd rather I repost the question in here, please let me know.

--
You received this message because you are subscribed to the Google Groups "vert.x" group.
To unsubscribe from this group and stop receiving emails from it, send an email to vertx+un...@googlegroups.com.
Visit this group at https://groups.google.com/group/vertx.
To view this discussion on the web, visit https://groups.google.com/d/msgid/vertx/bfc3a8b0-e648-49ef-9c76-a88537bfadb1%40googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Psycho Punch

unread,
Sep 21, 2016, 2:14:47 AM9/21/16
to vert.x
Julien,

Can you check my test set up if you think there are anything you think is irregular? Right now, I'm just trying to check that the Verticle is delegating properly to a mock object, but somehow, it fails.

@Rule
public final RunTestOnContext vertxRule = new RunTestOnContext();

@Before
public void setUp(TestContext context) {
    vertx
= vertxRule.vertx();
   
//verticle is set up with mock delegate before deployment
    vertx
.deployVerticle(verticle);
}

@After
public void tearDown(TestContext context) {
    vertx
.close(context.asyncAssertSuccess());
}

@Test
public void testDelegate(TestContext context) {
   
EventBus eventBus = vertx.eventBus();
   
Event event = new Event("id", "description")
    eventBus
.publish("event.channel", Json.encode(event));

   
//Mockito.verify
    verify
(delegate).invokeMethod(anyString(), anyString());
}

Julien Viet

unread,
Sep 21, 2016, 2:18:20 AM9/21/16
to ve...@googlegroups.com
verticle deploy is asynchronous,

you should do instead:

vertx.deployVerticle(verticle, context.asyncAssertSuccess());

so setup waits until the verticle is deployed and fails if the verticle deployment fails.

--
You received this message because you are subscribed to the Google Groups "vert.x" group.
To unsubscribe from this group and stop receiving emails from it, send an email to vertx+un...@googlegroups.com.
Visit this group at https://groups.google.com/group/vertx.

Psycho Punch

unread,
Sep 21, 2016, 2:29:08 AM9/21/16
to vert.x
That was what I had in my test code originally, but my problem with that was that the code doesn't seem to leave the @Before (setUp) method. It just hangs in there seeming to wait for anything...

Julien Viet

unread,
Sep 21, 2016, 2:30:57 AM9/21/16
to ve...@googlegroups.com
check your verticle deploy, it might not deploy correctly, for example you could have overriden start(Future) and not call future.complete()

On Sep 21, 2016, at 8:29 AM, Psycho Punch <rdg...@gmail.com> wrote:

That was what I had in my test code originally, but my problem with that was that the code doesn't seem to leave the @Before (setUp) method. It just hangs in there seeming to wait for anything...

--
You received this message because you are subscribed to the Google Groups "vert.x" group.
To unsubscribe from this group and stop receiving emails from it, send an email to vertx+un...@googlegroups.com.
Visit this group at https://groups.google.com/group/vertx.

Psycho Punch

unread,
Sep 21, 2016, 2:39:22 AM9/21/16
to vert.x
I have the following code in my Verticle. I'm just defining a handler for the event bus in here, and I'm not really sure where to call future.complete(). Originally, I had it after the definition of the handler, but it didn't seem to make any change; deployment still stalls...

private Delegate delegate;

@Override
public void start(Future<Void> future) throws Exception {
    vertx.eventBus().consumer("event.channel", message -> {
        logger.info("received!");
        Event event = Json.decodeValue(message.body().toString(), Event.class);
        delegate.invokeMethod(event.getId(), event.getDescription());
    });
}


Julien Viet

unread,
Sep 21, 2016, 3:13:24 AM9/21/16
to ve...@googlegroups.com
you can call complete() at the end of the method after the consumer registration or you can call it in the consumer completion (MessageConsumer#completionHandler(ar) method) handler if you want to wait that the message consumer registration is propagated accross the cluster

--
You received this message because you are subscribed to the Google Groups "vert.x" group.
To unsubscribe from this group and stop receiving emails from it, send an email to vertx+un...@googlegroups.com.
Visit this group at https://groups.google.com/group/vertx.

Thomas SEGISMONT

unread,
Sep 21, 2016, 4:01:27 AM9/21/16
to ve...@googlegroups.com
Just a comment on calling future.complete()

You MUST call it if you override #start(Future). Then you CAN either call it after the consumer registration or in the completion handler. I would do the latter though, since you can check for success to either complete or fail the future.

2016-09-21 9:13 GMT+02:00 Julien Viet <jul...@julienviet.com>:
you can call complete() at the end of the method after the consumer registration or you can call it in the consumer completion (MessageConsumer#completionHandler(ar) method) handler if you want to wait that the message consumer registration is propagated accross the cluster
On Sep 21, 2016, at 8:39 AM, Psycho Punch <rdg...@gmail.com> wrote:

I have the following code in my Verticle. I'm just defining a handler for the event bus in here, and I'm not really sure where to call future.complete(). Originally, I had it after the definition of the handler, but it didn't seem to make any change; deployment still stalls...

private Delegate delegate;

@Override
public void start(Future<Void> future) throws Exception {
    vertx.eventBus().consumer("event.channel", message -> {
        logger.info("received!");
        Event event = Json.decodeValue(message.body().toString(), Event.class);
        delegate.invokeMethod(event.getId(), event.getDescription());
    });
}



--
You received this message because you are subscribed to the Google Groups "vert.x" group.
To unsubscribe from this group and stop receiving emails from it, send an email to vertx+unsubscribe@googlegroups.com.

--
You received this message because you are subscribed to the Google Groups "vert.x" group.
To unsubscribe from this group and stop receiving emails from it, send an email to vertx+unsubscribe@googlegroups.com.

Psycho Punch

unread,
Sep 21, 2016, 7:23:29 AM9/21/16
to vert.x
I see. So that's what I was missing. Thanks!

Here's the Verticle code that now works for me:

private Delegate delegate;

@Override
public void start(Future<Void> future) throws Exception {
    vertx.eventBus().consumer("event.channel", message -> {
        logger.info("received!");
        Event event = Json.decodeValue(message.body().toString(), Event.class);
        delegate.invokeMethod(event.getId(), event.getDescription());

    }).completionHandler(result -> {
if (result.succeeded()) {
future.complete();
} else {
future.fail(result.cause());
}
});
}


Psycho Punch

unread,
Sep 21, 2016, 7:44:50 AM9/21/16
to vert.x
I just noticed though that I'm getting an IllegalStateException:

java.lang.IllegalStateException: Result is already complete: succeeded
    at io
.vertx.core.impl.FutureImpl.checkComplete(FutureImpl.java:164) ~[vertx-core-3.3.3.jar:na]
    at io
.vertx.core.impl.FutureImpl.complete(FutureImpl.java:108) ~[vertx-core-3.3.3.jar:na]
    at io
.vertx.core.impl.FutureImpl.handle(FutureImpl.java:135) ~[vertx-core-3.3.3.jar:na]
    at io
.vertx.core.impl.FutureImpl.handle(FutureImpl.java:23) ~[vertx-core-3.3.3.jar:na]
    at io
.vertx.core.eventbus.impl.EventBusImpl.lambda$callCompletionHandlerAsync$2(EventBusImpl.java:340) ~[vertx-core-3.3.3.jar:na]
    at io
.vertx.core.impl.ContextImpl.lambda$wrapTask$2(ContextImpl.java:316) ~[vertx-core-3.3.3.jar:na]
    at io
.netty.util.concurrent.AbstractEventExecutor.safeExecute(AbstractEventExecutor.java:163) ~[netty-common-4.1.5.Final.jar:4.1.5.Final]
    at io
.netty.util.concurrent.SingleThreadEventExecutor.runAllTasks(SingleThreadEventExecutor.java:418) ~[netty-common-4.1.5.Final.jar:4.1.5.Final]
    at io
.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:440) ~[netty-transport-4.1.5.Final.jar:4.1.5.Final]
    at io
.netty.util.concurrent.SingleThreadEventExecutor$5.run(SingleThreadEventExecutor.java:873) ~[netty-common-4.1.5.Final.jar:4.1.5.Final]
    at java
.lang.Thread.run(Thread.java:745) ~[na:1.8.0_102]


Psycho Punch

unread,
Sep 21, 2016, 8:38:22 AM9/21/16
to vert.x
Aaand my test code is suddenly failing again. I'm not really sure if there's a problem with test code involving asynchronous operations. Can you check if the part of the test code that sends messages to the event bus is guaranteed to run after the Verticle in test is fully deployed?

Thomas SEGISMONT

unread,
Sep 21, 2016, 9:14:51 AM9/21/16
to ve...@googlegroups.com
This is a known issue and it is fixed in master already. IIRC it is not harmful (ugly logs taken appart)

--
You received this message because you are subscribed to the Google Groups "vert.x" group.
To unsubscribe from this group and stop receiving emails from it, send an email to vertx+unsubscribe@googlegroups.com.
Visit this group at https://groups.google.com/group/vertx.

Psycho Punch

unread,
Sep 21, 2016, 12:21:21 PM9/21/16
to vert.x
Thomas,

Yeah, I saw the conversation on the issue over at GitHub. Any guesses as to why my test is still failing?

Thomas SEGISMONT

unread,
Sep 21, 2016, 12:24:48 PM9/21/16
to ve...@googlegroups.com
Have you also changed your test code to use #deploy(Verticle, AsyncResult) ? Maybe your setup method completes before the verticle is deployed.

2016-09-21 18:21 GMT+02:00 Psycho Punch <rdg...@gmail.com>:
Thomas,

Yeah, I saw the conversation on the issue over at GitHub. Any guesses as to why my test is still failing?

--
You received this message because you are subscribed to the Google Groups "vert.x" group.
To unsubscribe from this group and stop receiving emails from it, send an email to vertx+unsubscribe@googlegroups.com.
Visit this group at https://groups.google.com/group/vertx.

Psycho Punch

unread,
Sep 21, 2016, 1:12:57 PM9/21/16
to vert.x
Yes, I have.

I've set up a project to demonstrate the issue I'm having. Please take a look if you have the time: https://github.com/psychopunch/vertx-eventbus-demo.

Psycho Punch

unread,
Sep 21, 2016, 4:25:26 PM9/21/16
to vert.x
So I discovered that my problem is due to the mock verification being done before the Verticle under test could call it. What I've done so far is to get an Async from the TestContext and call awaitSuccess on it before doing the verification:

@Test
public void testDelegate(TestContext context) {

   
final Async async = context.async();
    doAnswer
(args -> {
        async
.complete();
       
return null;
   
}).when(delegate).invokeMethod(anyString(), anyString());


   
EventBus eventBus = vertx.eventBus();
   
Event event = new Event("id", "description")
    eventBus
.publish("event.channel", Json.encode(event));

   
//Mockito.verify

    async
.awaitSuccess();
    verify
(delegate).invokeMethod(anyString(), anyString());
}


I wonder though if there is a better way to do this...

Thomas SEGISMONT

unread,
Sep 22, 2016, 3:57:57 AM9/22/16
to ve...@googlegroups.com
I don't think there is a "better" way. Looks fine to me. You're testing an asynchronous program so it requires a bit of coordination.

--
You received this message because you are subscribed to the Google Groups "vert.x" group.
To unsubscribe from this group and stop receiving emails from it, send an email to vertx+unsubscribe@googlegroups.com.
Visit this group at https://groups.google.com/group/vertx.

Deven Phillips

unread,
Sep 22, 2016, 6:59:27 AM9/22/16
to vert.x
I too was having some problems with this and ended up switching over to using Spock with it's async coordination capabilities... Example below:

import io.vertx.core.logging.Logger
import io.vertx.core.logging.LoggerFactory
import io.vertx.groovy.core.Vertx
import io.vertx.groovy.core.http.HttpServer
import spock.lang.Shared
import spock.lang.Specification
import spock.util.concurrent.AsyncConditions


import static groovy.json.JsonOutput.prettyPrint
import static groovy.json.JsonOutput.toJson


/**
 * Test the Authenticator Verticle
 */

class AuthenticatorSpec extends Specification {
   
@Shared Logger LOG


   
@Shared def GOOD_TOKEN = 'GOODTESTTOKEN'
   
@Shared def BAD_TOKEN = 'BADTESTTOKEN'
   
@Shared HttpServer server


   
@Shared def userSuccess = '''{
  "type": "cc.v3.user.user",
  "self": "https://user.mycompany.com/v3/user/deven.phillips%40mycompany.com",
  "name": {
    "given": "Deven",
    "family": "Phillips"
  },
  "email": "deven.p...@mycompany.com",
  "settings": {},
  "roles": {
    "read": true,
    "modify": true,
    "create": false,
    "delete": false,
    "admin": false,
    "replication.source": true
  }
}'''



   
@Shared Vertx vertx


   
def setupSpec() {
       
System.setProperty('vertx.logger-delegate-factory-class-name', 'io.vertx.core.logging.Log4j2LogDelegateFactory')
        vertx
= Vertx.vertx()
        LOG
= LoggerFactory.getLogger(AuthenticatorSpec)
       
def config = [
            userServiceHost
: 'localhost',
            userServicePort
: 1024 + ((int)(Math.random() * 1000)),
            userServiceSSL
: false
       
]
       
def deployment = [config: config]
        vertx
.exceptionHandler({ e ->
            LOG
.error("Unhandled exception", e)
       
})


       
def conditions = new AsyncConditions(2)


        LOG
.info('Deploying Authenticator verticle')
        vertx
.deployVerticle('groovy:com.mycompany.cc.indexer.workers.Authenticator', deployment) { res1 ->
            conditions
.evaluate {
               
def status = res1.succeeded()
                LOG
.debug("Verticle Status: ${status}")
               
assert status
           
}
       
}


        LOG
.info('Create Mock user service')
        server
= vertx.createHttpServer().requestHandler({ req ->
           
def headers = prettyPrint(toJson(req.headers()))
            LOG
.info("HEADERS: ${headers}")
           
def authHeader = req.headers().get('Authorization')
           
if (authHeader && authHeader =~ /^Bearer .*$/) {
                LOG
.debug(authHeader)
               
def token = authHeader.replaceAll(/^Bearer /, '')
                LOG
.debug("TOKEN: ${authHeader}")
               
if (token == GOOD_TOKEN) {
                    req
.response().putHeader('Content-Type', 'application/json').end(userSuccess)
               
} else if (token == BAD_TOKEN) {
                    req
.response().setStatusCode(404).setStatusMessage('NOT FOUND').end()
               
} else {
                    req
.response().setStatusCode(400).setStatusMessage('BAD REQUEST').end()
               
}
           
}
       
}).listen(config.userServicePort, '0.0.0.0') { res2 ->
            conditions
.evaluate {
               
def status = res2.succeeded()
                LOG
.debug("HTTP Status: ${status}")
               
assert status
           
}
       
}
        conditions
.await(15)
   
}


   
def "Test mock user service with GOOD token"() {
        given
: "A mock user service and an auth verticle"
           
def conditions = new AsyncConditions(1)


       
when: "We send a message to the verticle, we expect a positive result"
            vertx
.eventBus().send('cc.v5.auth.user', GOOD_TOKEN) { reply1 ->
                conditions
.evaluate {
                   
def status = reply1.succeeded()
                    LOG
.debug("GOOD Status: ${status}")
                   
assert status
               
}
           
}
       
then:
            conditions
.await(5)
            noExceptionThrown
()
   
}


   
def "Test mock user service with BAD token"() {
        given
: "A mock user service and an auth verticle"
           
def conditions = new AsyncConditions(1)


       
when: "We send a message to the verticle, we expect negative result"
            vertx
.eventBus().send('cc.v5.auth.user', BAD_TOKEN) { reply2 ->
                conditions
.evaluate {
                   
def status = reply2.failed()
                    LOG
.debug("BAD Status: ${status}")
                   
assert status
               
}
           
}
       
then:
            conditions
.await(5)
            noExceptionThrown
()
   
}


   
def cleanupSpec() {
       
def conditions = new AsyncConditions(1)
        vertx
.close() { res3 ->
            conditions
.evaluate {
                   
def status = res3.succeeded()
                    LOG
.debug("NO Status: ${status}")
                   
assert status
           
}
       
}
        conditions
.await(5)
   
}
}



Psycho Punch

unread,
Sep 22, 2016, 8:17:28 AM9/22/16
to vert.x
Deven,

Thanks for the suggestion. I really like Spock, but I'm currently limited to using just Java (8) at the moment. Also, while I'm not very familiar with the async support in Spock, it looks very similar to what vertx-unit has out of the box. I'm currently using the latch-like mechanism of Async using TestContext.async(int).

Deven Phillips

unread,
Sep 22, 2016, 9:23:58 AM9/22/16
to vert.x
Everyone has to do things how they see best, so if Spock doesn't suit your needs . . . That said, Spock's use of Groovy would not "pollute" your actual project because it would be a "test" scoped dependency. Your build tool (Gradle or Maven) would make it pretty easy to integrate.

Cheers!

Deven
Reply all
Reply to author
Forward
0 new messages