Monday, December 31, 2012

TypeScript Node.js Development Part 5 - Unit Tests and Debugging

When I started thinking about doing some Node.js development, I wasn't sure if I would be able to easily do unit testing and debugging. Pretty much all the Javascript stuff I'd done in the past were not tested at all and used console.log as the only form of debugging. That was fine for the front-end scripts I was writing, but for "serious business" backend code, testing and debugging were pretty important to me. I was pleased to find out that this is something that is very possible in Node, and although I don't think there's a way to debug through TypeScript code yet, debugging through the compiled Javascript can be done, and that should be enough for me.

Let's start with debugging, which is actually quite simple to do using Node Inspector and Chrome (or any other WebKit browser). First we need to install Node Inspector, I install this globally since it's something that I want for all my node projects:
npm install -g node-inspector

We now start up our app in debug mode:
node --debug app

And, in a separate console window, start up Node Inspector:
node-inspector

If that all works, you'll be able to open Chrome, go to your app (e.g. http://localhost:1337/) and in another tab, go to the inspector page (e.g. http://localhost:8080/debug?port=5858). What you see on the inspector page should be pretty familiar if you've ever used Chrome's Javascript debugging tools.


If you play around with this a bit, you'll see that you can do anything you would normally do when debugging programs - add breakpoints, watch variables, check the call stack, run code in the console, etc. You can even step into all the node packages you're using, which can be very useful. The one thing that is missing is debugging through TypeScript code rather than the compiled Javascript code.

The intended solution for this is to generate source maps for the TypeScript code, and you can do this as part of the usual Visual Studio build by editing the "BeforeBuild" target command to be:
<Target Name="BeforeBuild">
    <Exec Command="&quot;$(PROGRAMFILES)\Microsoft SDKs\TypeScript\0.8.0.0\tsc&quot; -sourcemap @(TypeScriptCompile ->'&quot;%(fullpath)&quot;', ' ')" />
</Target>

I'm sure that can be done through Visual Studio somehow but I just hand-edit the .csproj file. After this is enabled, building the project will generate .js.map files as well as the .js files for each .ts file. These .map files allow debuggers to interpret the Javascript code as TypeScript, but unfortunately this is not supported in node-inspector yet. The bright side is that it does work on client-side code, as long as you enabled source maps in Chrome:


For unit testing, there are a number of options including Vows, Expresso, Nodeunit, and even just the built-in  assert functions, but in the end I decided to go with Mocha. I must admit that the main reason for my choice was because there were already TypeScript bindings for it in github, but after using it for a while I'm pretty happy with the way it works, especially because it is very easy to test synchronous as well as asynchronous code.

To get started, let's create a simple class (Dog.ts) with only a constructor that takes a parameter, and an accessor for that member:
export class Dog {
    constructor (private name: string) { }
    getName() { return this.name; }
}

Now, after adding mocha.d.ts to our definitions list, we can write a simple test for this class (tests/dogTest.ts):
///<reference path='../app.d.ts' />

import assert = module("assert")
import dog = module("../Dog")

describe('Dog', function () {
    describe('#getName()', function () {
        it("should return the name it's constructed with", function () {
            var fido = new dog.Dog("Fido");
            assert.equal(fido.getName(), "Fido");
        });
    });
});

To run this test, all we need to do is install mocha globally, then run the mocha command in our program directory, and it will take care of finding and running the tests for us:
>npm install -g mocha
>mocha
.
 
 √ 1 test complete (1 ms)

But what if you have some asynchronous code, such as a function with a callback, and you want to ensure that the callback is called before the test ends? Mocha handles that with an optional callback of its own called done, if this callback is present in an "it", the test will not succeed until it is called. As an example,  we can add this function to our dog class:
fetch(gotTheBall: () => any) { setInterval(() => gotTheBall(), 100); }

And a test for this function:
    describe('#fetch()', function () {
        it("should get the ball, good dog!", function (done) {
            var fido = new dog.Dog("Fido");
            fido.fetch(() => {
                done();
            });
        });
    });

If the done function was not present here, the test would end without waiting for the dog to fetch the ball, which would be very unsatisfying.

So that's how you write a simple synchronous and asynchronous tests, but in more complex tests, sometimes you need to mock out a certain dependency and assert that a function is called on it, and sometimes you need to check that a HTTP request is made but you don't actually want to make that request. Node has solutions for this too, and there are several options, but what I decided to use was Gently for general mocking/stubbing, and Nock for mocking HTTP requests (which I usually make by using the request package). Currently, only definitions for request are in the typescript-node-definitions repo, but I've made a fork which contains definitions for Gently and Nock, although by the time you read this that might already have been pulled in. Add these packages to your package.json file, and the definitions to your add.d.ts file, and we're ready to go.

Both of these packages have good documentation and readable code, so to avoid this post getting too much longer, I'll just give you some sample code. Firstly, to test Gently, we'll give our dog an external voice box.
export class Dog {
    constructor (private name: string, private woofer?: Woofer) { }
    ....
    bark() { this.woofer && this.woofer.woof("woof"); }
}
export class Woofer { 
    public woof(sound: string) { console.log(sound); }
}

Then we'll write a test which will mock out the woofer:
import gently = module("gently")
...
    describe('#bark()', function () {
        it("should invoke the woofer", function () {
            var woofer = new dog.Woofer();
            var fido = new dog.Dog("Fido", woofer);
            var mock = new gently();
            mock.expect(woofer, "woof", 1, (sound) => {
                assert.equal(sound, "woof");
            });
            fido.bark();
            mock.verify();
        });
    });

What's happening here is that we're using Gently to say that we expect woofer.woof to be called once, with "woof" as the parameter. If you run this test, you'll notice that it passes, but the console.log never happens, which is what we would expect to happen since it has been mocked out by Gently. Note that the call to verify is optional, since that happens automatically when the gently object goes out of scope, but it is left in here for clarity.

Now to test Nock, we'll get our dog to search for the body of a missing person (using Google of course), but we'll use Nock to make sure that no requests are actually made and so that we can be sure we know what the web request will return.
///<reference path="./app.d.ts">
import request = module("request")
export class Dog {
    ...
    search(done: (string) => void) { 
        request("http://www.google.com/?q=missing%20person", (err, response, body) => { 
            (!err && response.statusCode == 200) ? done(body) : done(null);
        });
    }
}
...
...
import nock = module("nock")
...
describe('Dog', function () {
    ...
    describe('#search()', function () {
        it("should return body if found", function (done) {
            var fido = new dog.Dog("Fido");
            nock("http://www.google.com")
                .get("/?q=missing%20person")
                .reply(200, "found it");
            fido.search((result) => { 
                assert.equal("found it", result); 
                done(); 
            });
        });
        it("should return null if not found", function (done) {     
            var fido = new dog.Dog("Fido");
            nock("http://www.google.com")
                .get("/?q=missing%20person")
                .reply(404);
            fido.search((result) => { 
                assert.equal(null, result); 
                done(); 
            });
        });
    });
});

Gently and Nock have many more features of course, but this should give you an idea of how handy they can be when writing tests.

One more thing I want to go over is to tie together the theme of this post and show you how to debug these tests. Using all that we've covered in this post, debugging is easy. All you need to do is run mocha and get it to automatically put a breakpoint on the first line, then use node inspector.
> mocha --debug-brk
debugger listening on port 5858
> node-inspector
visit http://0.0.0.0:8080/debug?port=5858 to start debugging

Open up your browser and go to the normal node-inspector address (http://localhost:8080/debug?port=5858). In an ideal world you would be able to see your test code in the list of scripts, but unfortunately this was not the case for me and I had to do a few hacks to get it to work (hopefully this will be fixed soon). My problem is that the debugger starts debugging the mocha source code but doesn't let me see my test code and add breakpoints there, so I had to manually add a breakpoint by adding "debugger;" to the top of my test file. After I did this, I was able to continue the debugger after the mocha code, and then see my code when the second breakpoint was added. From this point you can add breakpoints to and debug your code as usual through the node inspector page.

Wow that was a long post, the next and final part will cover using AppFog rather than Azure and it will be a lot shorter. Here is the final Visual Studio template I'll provide, which should have all the code up to the end of this post.

Previously: TypeScript Node.js Development Part 4 - More Packages, next: TypeScript Node.js Development Part 6 - AppFog Deployment.

No comments:

Post a Comment