11/21/2013

Jasmine-Node and Rewire, Part 2: Making the test complete

In the previous post I introduced the concept of using Rewire with Jasmine-Node to simplify Node.js unit testing. Rewire added a __set__ and a __get__ function to the module which let us grab a function in the module, and test it directly. We could test the function's output by controlling the objects that the function worked with, and checking them upon function completion. We didn't need to manipulate any internals of the function so our testing is still honoring the "Black Box" concept of unit testing. The idea is that the unit test only cares about what the testee can take in, return, and manipulate outside itself. How that operation is performed is not important to the unit test.

There was one test we didn't look at which I want to discuss now. Our simple server is set up to make an http request to a second endpoint when a certain parameter is passed in the query string. Since we are reaching into the module, but not actually running inside it's internals, we can't stop that http call. So how can we test that function without sending out the http call?

We could set the call to an innocuous location that we control, but that's messy and what if we need to examine the outgoing information. This is the part of the function we want to test:

  else if (req.query.callback && req.query.callback !== ''){
      http.get('http://test.test', function (response){
          res(response);
      })
      .on('error', function (e){
          res(e);
      })

The server will process this part of the if statement if "callback" appears in the querystring. The server sends a GET request to http://test.test, and runs the callback function upon response. We can't directly manipulate the callback itself. We don't really want to risk having the get call be sent to the remote server. This means we can't use our earlier testing methodology. If we allow the get call to be made, and process the response that would constitute integration testing. It would be really useful if we could control the get function itself. So, how do we handle the get function and make it work for us? The answer is one of those awesome things about JavaScript and Rewire. Since everything in JavaScript is an object (mostly) that can be manipulated, then we can use Rewire to replace the get function on http with one of our own functions. We aren't manipulating the internals of the function directly. We are manipulating one of its dependencies which should be fair game.

Here is our test:

    it('If provided a callback parameter, an http get request should be issued', function(done) {
        var spy = spyOn (http, 'get').andCallFake (function(){
            expect (spy).toHaveBeenCalled();
            done();
            return {
                on: function (){}
            }
        }),
        req = {
            query: {
                callback: 'callMe'
            }
        };
        loadMethod(req, {});
    });
});

This test uses a standard jasmine spy to observe the http object. Thanks to Rewire we are observing the http object inside of the server code.

If you look back to the top of our test, we can see where we rewired the server code, and reached inside it to get the http module.

    server = rewire(__dirname + '/server.js'),
    http = server.__get__('http');

The spy will interfere with any calls to http.get called inside the server's context, and run the function from the test context instead. Our function will include the done() callback that jasmine needs to complete the test, as well as our expect statement. We complete the test by calling the loadMethod function, and passing in the parameters it needs to catch the correct control path.

We have kept our test clean and tested the server code without modifying it. We reached in to grab it's functions, and even control it's dependency. However, we did not modify how it runs. We are able to completely test the function with the certainty that it will run exactly the same way in the wild.

Go to Part 1