How Node.js Makes Network Code More Testable

Introduction

Node.js, a server platform built on Chrome’s JavaScript engine, is changing the face of web development. While Node.js itself is fast and scalable, the open source community surrounding Node.js is constantly discovering new ways to make application development more productive. This article will show how Node.js network code can be easier to write automated tests for, resulting in more reliable software.

JavaScript On Both Sides

One of the most interesting advantages of a Node.js stack is using the same language on the client and server. Sharing code like this has led to amazing tools like Rendr which lets the server deliver fully rendered HTML while the client can rerender parts of the page with the same templates.

I love how easy automated testing can be in this environment. When you are writing network code, often enough you have to mock network responses on both sides in order to write tests. Writing mocks is time-consuming: I’d rather be writing tests! Furthermore, writing tests against mocks can be less accurate, allowing unexpected bugs to sneak by. When your client and server are in the same language, however, they can be run in the same environment at the same time, avoiding the need for mocks.

Test Everything At Once

I am currently working on a project called Cloak, which is a network layer for multiplayer HTML5 games that handles lobbies, rooms, chat, reconnection to games, and so on. Cloak has both a client and a server component and handles all the communication between them. Traditionally this would be a huge hassle to write automated tests for, but Node.js has made it easy. Our tests use Nodeunit to run the actual Cloak server and actual Cloak clients at the same time! For example, one test configures a server to automatically create rooms when two users connect, then fires up two clients, connects them both, and confirms that a new room was created for them.

I have always been in favor of writing the types of tests that are most valuable for the application they’re testing. What I just described might be called integration testing (as opposed to unit testing), and to me that is exactly the kind of testing Cloak needs in order to really build confidence in the correctness of the library. Integration testing can be quite challenging on many platforms. Consequently, I’m all the more pleased with my results doing integration testing for Cloak.

Let’s Look At Some Code

The Nodeunit tests have some setUp and tearDown code so that tests don’t have to worry about keeping the testing environment tidy. Here is our setUp. Not much happens here besides providing default values and an empty client list.

// setUp is called before every test
// Pepare a server and an empty client list
setUp: function(callback) {
  try {
    this.port = 8091;
    this.host = 'http://localhost:' + this.port;
    this.server = cloakServer;
    clients = [];
    callback();
  }
  catch(e) {
    console.error(e);
  }
},

This is a helper method called createClient that tests can use. Notice it uses the clients array we initialized in setUp.

// Used in tests to create a new Cloak client. Using
// this function instead of doing it manually means
// clients will be properly cleaned up after tests
// are done.
function createClient() {
  var client = createCloakClient();
  client._setLibs(_, io);
  clients.push(client);
  return client;
}

The tearDown is a little more involved because it has to handle different states: the test may or may not have disconnected the clients.

// tearDown is called after every test
// Shut down server and all clients
tearDown: function(callback) {
  try {
    _(clients).forEach(function(client) {
      if (client.connected()) {
        console.log('ending client');
        client.end();
      }
      else {
        console.log('client already disconnected');
      }
    });
    clients = null;
    console.log('stopping server');
    this.server.stop(function() {
      console.log('server stopped');
      callback();
    });
  }
  catch(e) {
    console.error(e);
  }
},

Now let’s look at an actual test. This one tests the basics of messaging between the client and server. Notice we use this.server and createClient from earlier. configure and run are parts of the Cloak API. Normally the client and server would be configured and run in the browser and Node.js respectively.

// Test basic messaging, to and from client
messageBasics: function(test) {

  test.expect(2);

  var server = this.server;
  var client = createClient();

  server.configure({
    port: this.port,
    messages: {
      dog: function(arg, user) {
        test.equals(arg.foo, 123);
        user.message('cat', {bar: 456});
      }
    }
  });

  client.configure({
    serverEvents: {
      begin: function() {
        client.message('dog', {foo: 123});
      }
    },
    messages: {
      cat: function(arg) {
        test.equals(arg.bar, 456);
        test.done();
      }
    }
  });

  server.run();
  client.run(this.host);

},

So, when this test is run, the following things happen:

  1. A Node.js server is configured and run using Cloak.
  2. A Cloak client is configured and run, connecting to the server.
  3. The server’s begin event handler sends the dog message to the client.
  4. The client’s dog message handler asserts the message parameter is correct. Then it sends the cat message to the server.
  5. The server’s cat message handler asserts the message parameter is correct. Then the test is complete.

Conclusion

Node.js makes it easier to write tests for network code, which means delivering stable software faster. Here’s to testing being easier and more effective than ever!

Comments

Contact Us

We'd love to hear from you. Get in touch!

Phone

+1 617-283-2807

Mail

P.O. Box 961436
Boston, MA 02196