TDDing a Vert.x-Chat in Javascript (Pt.2)

In the first part of this tutorial we installed Vert.x, did the setup necessary to TDD an application with Vert.x, wrote and executed our first passing test with qunit.

In this part we will have a look at how to send around messages via the Vert.x-EventBus.

What we have

This is the state of our test-module we have after Part 1 of the tutorial:

mods/com.firstiwaslike~chat-test~0.0.1  
├── main.js
├── mod.json
└── test_chat.js

We also have one passing test that makes sure we can use Vert.x from within our Javascript-code.

What we want

We want to build a chat application in Vert.x so it would be great if our Vert.x backend could actually send chat messages to users of a chat. To prototype the simplest thing possible we will only have one chat room called chat.public and no users. Everybody that can connect to the chat room can publish messages in the chat room and everybody connected to the chat room will receive messages that are published in the chat room.

Developing the Chat

All this should be done via the Vert.x-EventBus so the first thing we have to do is t make sure that there is actually an EventBus we can connect to:

test('vert.x eventbus can be required', function() {  
  var eb = vertx.eventBus;
  ok(eb, 'eventbus is defined');
});

This passes!

Loading testfile: test_chat.js  
----------------------------------------
Vert.x-TDD  
----------------------------------------
 PASS - requiring vertx works in testfile
 PASS - vert.x eventbus can be required
----------------------------------------
    PASS: 2  FAIL: 0  TOTAL: 2
    Finished in 184 milliseconds.
----------------------------------------

Working with the EventBus is simple. You subscribe to an address on the EventBus and define a handler that gets executed everytime a message is sent to the address you subscribed to. The handler gets passed the message that was sent and you can then handle the message. It is also possible to reply to messages etc.

It is not useful to test if we can send messages via the EventBus (this is behaviour that Vert.x provides) but we can check if anything handles a message sent to chat.public by waiting for a reply:

asyncTest('messages send to `chat.public` receive a reply', function() {  
  var eb = vertx.eventBus;
  var message = { body: "Hello World!" };

  // to fail the test after a specified time
  var fail = function() {
    ok(false, 'messages receive a reply from `chat.public`');
    start();
  };

  // send a message to `chat.public`. The reply
  // handler lets the test succeed and cancels the
  // timer that would otherwise fail the test
  eb.send('chat.public', message, function(reply) {
    ok(true, 'received a reply!');
    vertx.cancelTimer(timerID);
    start();
  });

  // setup the fail-callback to execute after 50ms
  // to make sure we don't wait infinite for the
  // asyncTest to complete
  var timerID = vertx.setTimer(50, fail);
});

Of course this test case fails. We don't have anything yet that handles messages sent to chat.public.

Loading testfile: test_chat.js  
----------------------------------------
Vert.x-TDD  
----------------------------------------
 PASS - requiring vertx works in testfile
 PASS - vert.x eventbus can be required
Succeeded in deploying module  
 FAIL - messages send to `chat.public` receive a reply
    | OK | messages receive a reply from `chat.public`
----------------------------------------
    PASS: 2  FAIL: 1  TOTAL: 3
    Finished in 261 milliseconds.
----------------------------------------

Handling messages on the EventBus should be done by a Vert.x-verticle. This is the simplest small unit of code that does one thing in Vert.x. Multiple verticles make up a module etc.. You can have a look at the Vert.x documentation for more detailed information.

Then let's write a verticle that handles messages send to chat.public. Just create a file chat_verticle.js in the root of your project:

var vertx = require('vertx');  
var eb = vertx.eventBus;

var address = 'chat.public';  
var handler = function(message, replier) {  
  replier('ok');
};

eb.registerHandler(address, handler);  

You can run this verticle via the vertx run-command. vertx run chat_verticle.js will run it.

But something is wrong. When running the chat-verticle and then executing the tests the third test is still failing. This is caused by the fact that both started Vert.x instances work with their own EventBus not with a distributed one.

To make sure both running Vert.x instances (one is running the chat handler and the other one is running the tests) use the same EventBus you can pass the -cluster option to both vertx run(mod)-calls.

vertx run chat_verticle.js -cluster

vertx runmod com.firstiwaslike~chat-test~0.0.1 -cluster

With this in place you get the following output:

Loading testfile: test_chat.js  
----------------------------------------
Vert.x-TDD  
----------------------------------------
 PASS - requiring vertx works in testfile
 PASS - vert.x eventbus can be required
Succeeded in deploying module  
 PASS - messages send to `chat.public` receive a reply
----------------------------------------
    PASS: 3  FAIL: 0  TOTAL: 3
    Finished in 319 milliseconds.
----------------------------------------

Great the test is passing!

We are almost finished with our chat backend. We can send messages to chat.public and we are already receiving a reply from our chat-verticle. We are only missing the message publishing.

To wrap up with the second part of this tutorial series we will write a test that makes sure we are actually publishing the message after receiving it in our chat-verticle:

asyncTest('messages send to `chat.public` get published', function() {  
  var eb = vertx.eventBus;
  var messageToSend = { body: 'Hello World!'};
  var fail = function() {
    ok(false, 'message gets published');
    start();
  };

  var handler = function(message) {
    deepEqual(message, messageToSend, 'message is published in `chat.public`');
    start();
  };

  eb.send('chat.public', messageToSend);

  var timerID = vertx.setTimer(500, fail);
});

Of course this fails. Additionally this code is bad for multiple reasons. Obviously this is not dry at all and needs some refactoring. We are practically using the same fail callback as in the third test. We are using the variable eb over and over again instead of setting it up before running all tests etc. We will come to this in a second.

But what should be obvious by now is that definately something is wrong with our understanding of the chat. When sending our messages to chat.public and then expecting the message to be published on chat.public by our handler we will end up in an infinite loop for sure. Also this will not scale to multiple chat rooms. It would be much more practical to send messages to our chat handler via an address and the chat handler would then decide where the message belongs to. This will enable us in the future to include more logic in the message handler too (e.g. Authorization for channels).

I adapted the tests accordingly:

require('jslibs/qunit/qunit/qunitContext')(this);

var vertx = require('vertx');  
var eb = require('vertx/event_bus');

var FAIL_TIMEOUT = 500;  
var SEND_ADDR = 'chat.send';  
var CHAT_ADDR = 'chat.public';  
var TEST_MESSAGE = { body: "Hello World!", address: CHAT_ADDR };

var fail = function(message) {  
  return function() {
    ok(false, message);
    start();
  }
};

QUnit.module('Vert.x-TDD');

test('requiring vertx works in testfile', function() {  
  ok(vertx, 'vertx is defined');
});

test('vert.x eventbus can be required', function() {  
  ok(eb, 'eventbus is defined');
});

asyncTest('messages send to `'+SEND_ADDR+'` receive a reply', function() {  
  eb.send(SEND_ADDR, TEST_MESSAGE, function(reply) {
    ok(true, 'received a reply!');
    vertx.cancelTimer(timerID);
    start();
  });

  var timerID = vertx.setTimer(FAIL_TIMEOUT, fail('messages receive a reply'));
});

asyncTest('messages send to `'+SEND_ADDR+'` get published to `'+CHAT_ADDR+'`', function() {  
  var handler = function(msg) {
    deepEqual(msg, TEST_MESSAGE, 'message is published in `'+CHAT_ADDR+'`');
    vertx.cancelTimer(timerID);
    start();
  };

  // wait for the handler to register before sending
  // a message
  eb.registerHandler(CHAT_ADDR, handler, function() {
    eb.send(SEND_ADDR, TEST_MESSAGE);
  });

  var timerID = vertx.setTimer(FAIL_TIMEOUT, fail('message gets published'));
});

Now we are back to the third test failing and also the fourth is still failing.

----------------------------------------
Vert.x-TDD  
----------------------------------------
 PASS - requiring vertx works in testfile
 PASS - vert.x eventbus can be required
Succeeded in deploying module  
 FAIL - messages send to `chat.send` receive a reply
    | OK | messages receive a reply
 FAIL - messages send to `chat.send` get published to `chat.public`
    | OK | message gets published
----------------------------------------
    PASS: 2  FAIL: 2  TOTAL: 4
    Finished in 710 milliseconds.
----------------------------------------

This is not a huge surprise. Our chat handler is listening on the wrong address. If we change the address it registers its handler on to chat.send the third test is passing again.

var vertx = require('vertx');  
var eb = vertx.eventBus;

var SEND_ADDRESS = 'chat.send';

var handler = function(message, replier) {  
  replier('ok');
};

eb.registerHandler(SEND_ADDRESS, handler);  
----------------------------------------
Vert.x-TDD  
----------------------------------------
 PASS - requiring vertx works in testfile
 PASS - vert.x eventbus can be required
Succeeded in deploying module  
 PASS - messages send to `chat.send` receive a reply
 FAIL - messages send to `chat.send` get published to `chat.public`
    | OK | message gets published
----------------------------------------
    PASS: 3  FAIL: 1  TOTAL: 4
    Finished in 586 milliseconds.
----------------------------------------

To get the last test passing and to wrap it up for the second part of the tutorial we have to publish the message received on chat.send to the address that is specified in the received message:

var vertx = require('vertx');  
var eb = vertx.eventBus;

var SEND_ADDRESS = 'chat.send';

var handler = function(message, replier) {  
  replier('ok');
  eb.publish(message.address, message);
};

eb.registerHandler(SEND_ADDRESS, handler);  
----------------------------------------
Vert.x-TDD  
----------------------------------------
 PASS - requiring vertx works in testfile
 PASS - vert.x eventbus can be required
Succeeded in deploying module  
 PASS - messages send to `chat.send` receive a reply
 PASS - messages send to `chat.send` get published to `chat.public`
----------------------------------------
    PASS: 4  FAIL: 0  TOTAL: 4
    Finished in 358 milliseconds.
----------------------------------------

Yeah! And that's it! We now have working chat backend written in Vert.x. You can have a look at the complete sourcecode here.

In the next part of this tutorial I will walk you through the next step and build a small browser app that uses this backend to implement a chat in clients' browsers.

Tip: If you run into any problems with sending or publishing messages you can always debug your Vert.x code with console.log. For this you have to require Vert.x's console and log the stringified messages. Example:

var console = require('vertx/console');  
var handler = function(message) {  
  console.log(JSON.stringify(message));
};
comments powered by Disqus