10/21/2013

Jasmine-Node and Rewire, Part 1

Node.js development can be a lot of fun. Unit testing can be fun too. Unit testing Node can be very much not fun. Unit testing Nodejs modules often feels like trying to build a neutered integration test with mocks, nocks, and intercepts. Since Node is a web server whose functionality revolves around responding to http requests, the most natural testing method would be to trip those URLs and check the response. However, that ends up being more of a test of the middleware, and doesn't allow for testing the function directly. If you want to ignore the http calls, the most rational way to test the functions inside the module would be to export them. Once exported the tests can easily be run against the exported functions. The problem with exposing all the functions via export means functionality that should be private is no longer private.

Unit testing is not a good reason to break important development practices like encapsulation. What is needed is a way to reach the functions without exposing them directly. It would be really useful if we could reach into a module and play with its internals like a mad scientist teaching gross anatomy using live subjects. I have found such a mad scientist in Johannes(jhnns) and his Rewire library.

Jasmine-Node and Rewire are a powerful pair of Node testing tools. I prefer Jasmine-Node, but rewire can be used with any Node.js unit testing library. Rewire is used in place of require in the module's regular require block. Rewire still runs the require function so that there is no change to expected behavior. Instead, rewire adds two new methods, __get__ and __set__, to the returned object. The get and set functions can be used to interact with any variable inside the imported module's scope. Thanks to functions being objects in JavaScript we can interact with those too. The module remains accessible through all of it's normal public facing methods.

This post is going to walk through using rewire to unit test a Node server. The Node server is running Express and will be very simple. The code is accessible via Cloud9. It is also available via github. The node server:

var express = require('express'),
    app = express(),
    http = require ('http');
    
    app.use(express.bodyParser());
    
    app.get('*', pageLoader);
    
    function pageLoader (req, res){
        if (req.query.message && req.query.message !== '' ){
            res.end('Found this message: ' + req.query.message);
        } else if (req.query.callback && req.query.callback !== ''){
            http.get('http://test.test', function (response){
                res(response);
            })
            .on('error', function (e){
                res(e);
            })
        } else {
            res.end('No message found.');
        }
    };
    
    app.listen(process.env.PORT);

The top part of the module is standard express boilerplate code. We add express middleware and handle all get requests with the same pageLoader function. The last line sets up the server to listen to the appropriate port.

The pageLoader function is also very simple for the purpose of this example. If the query string in the request has a message parameter, then display it. If the the query string has a callback parameter, make a server side http request to a remote server. If neither parameter is provided then display a 'no message found' message. Our first set of tests will be to confirm the pageLoader's processing of the message parameter works as expected.

var rewire = require('rewire'),
    server = rewire(__dirname + '/server.js'),
    loadMethod = server.__get__('pageLoader');

Our Jasmine-Node spec will start with three variables. The first require statement pulls rewire into the test. The second statement,

    server = rewire(__dirname + '/server.js')
does two operations. The rewire call functions just like a require call and pulls the module into the code's context. The second thing it does is expose the variables and functions of the module via __get__ and __set__ calls. The third statement
loadMethod = server.__get__('pageLoader');
is going to give us access to the loadMethod inside of server.js.

Let's take a look at the first two tests in our suite of three. One test is going to confirm that a message parameter in the query string outputs as expected. The other test will check for the situation when no message is passed in the query string.

describe('Test page loader', function() {
    it('Should give me the message query string', function(done) {
        var req = {
            query: {
                message: 'Hi Tester'
            }
        },
        res = {
            end: function(msg) {
                expect(msg).toEqual('Found this message: Hi Tester');
                done();
            }

        };
        loadMethod(req, res);
    });

The first test simulates a message in the query string and expects the function to send that message to the response's end method. This is an asynchronous operation so the Jasmine-Node done function will need to be called when the response ends. The loadMethod function is called directly since it has been pulled out using the __get__ function. We know the code is expecting request and response objects as provided by express. However, we know the code doesn't need the whole object. It only needs the properties and methods it intends to use. Therefore, we can make our own objects and call the function directly and provide them.

  var req = {
            query: {
                message: 'Hi Tester'
            }
        }
The req object contains an object called query which contains a property called message with a string value. Passing that in the loadMethod function will allow this line to work:
if (req.query.message && req.query.message !== '' )
Secondly, we define a custom response object:
      res = {
            end: function(msg) {
                expect(msg).toEqual('No message found.');
                done();
Our response object contains one function called "end". This will allow the following line in loadMethod to work:
            res.end('Found this message: ' + req.query.message);

Our second test works the same as the first with the exception of an empty query object. The code will fail the first if check. The code will also fail the else if check. Finally, the else clause will catch it:
        } else {
            res.end('No message found.');
        }
and respond with 'No message found'.
    it('Should tell me when no message found', function(done) {
        var req = {
            query: {}
        },
        res = {
            end: function(msg) {
                expect(msg).toEqual('No message found.');
                done();
            }
        };
        loadMethod(req, res);
    });

Take a moment to fully consider the possibilities that rewire offers. No need to intercept an http request, and the function is tested without middleware interference. It is still important to test the result of the code passing through express, but I would suggest saving that for integration/end-to-end testing. This approach maintains the function as a black box, but it opens the container for examination under controlled circumstances. You can test directly and keep private things private.

Part 2 will look at the callback parameter, and how we handle the http request without allowing it to actually issue the request.