Quantcast
Channel: How To Node
Viewing all articles
Browse latest Browse all 134

Testing Private State and Mocking Dependencies

$
0
0

During Christmas I've been working on Testacular and found some tricks how to make my testing life easier. It's nothing special at all, just a simple way how to access private state of a module and how to mock out some dependencies. I've found these two techniques pretty usefull, so I believe it might help someone else as well...

Why would you need to access private state of a module?

Private should be private, right? Yes, for sure. But during unit tests, it can be very helpful to have access to private state of a module - I always try to cover functionality or bug at the lowest possible level, because it's simply cheaper:

  • faster test execution
  • less code is required to bootstrap the test

Let's say we are building very simple static web server, the skeleton might look something like this:

var http = require('http');

var handleRequest = function(request, response) {
  // read file from fs and send response
};

exports.createServer = function() {
  return http.createServer(handleRequest);
};

This module has only one public method createServer, so unless we make it public, we can't get hold of anything else but this method. That sucks, because HttpServer doesn't have any public method to call the handler, so we would have to send some data through socket to test it. That's way too much effort, especially when you realize that the only code we really need to test is the handleRequest function - everything else is just Node and we trust Node, because it's awesome. We need to test our code - that's where all the bugs are.

Why would you need to mock out dependencies?

Some dependencies are cheap, some not. When our code uses modules like util or path, we are fine. Nothing bad happens there. But when it comes to some other modules like fs, net or http, it's totally different story. We simply don't want to deal with real filesystem in unit tests. There are many reasons for that, such as:

  • accessing file system is slow
  • it requires seting up some state of filesystem
  • there is only one instance of filesystem, so conflicts between different unit tests might happen

So we want our module to use something different - we call these objects test doubles (I actually like using mock/stub/dummy definition from G.Meszaros). The question is, how can we persuade our awesome module, to use a different instance during testing and different instance in production?

Dependency Injection is great for this - it wires all the pieces together (yep, it saves us lot of work) - and more than that, it does allow us to use different instances during testing. Yep, DI is just awesome! I actually think, that new languages such as Dart should support DI natively - in the same way as they do support memory management.

Unfortunately, there is no DI in Node, at least I haven't found any sufficient implementation. Writing a Dependency Injection framework is definitely a solution, but I was looking for something faster...

Let's do it !

Module Loader

module-loader.js
var vm = require('vm');
var fs = require('fs');
var path = require('path');

/**
 * Helper for unit testing:
 * - load module with mocked dependencies
 * - allow accessing private state of the module
 *
 * @param {string} filePath Absolute path to module (file to load)
 * @param {Object=} mocks Hash of mocked dependencies
 */
exports.loadModule = function(filePath, mocks) {
  mocks = mocks || {};

  // this is necessary to allow relative path modules within loaded file
  // i.e. requiring ./some inside file /a/b.js needs to be resolved to /a/some
  var resolveModule = function(module) {
    if (module.charAt(0) !== '.') return module;
    return path.resolve(path.dirname(filePath), module);
  };

  var exports = {};
  var context = {
    require: function(name) {
      return mocks[name] || require(resolveModule(name));
    },
    console: console,
    exports: exports,
    module: {
      exports: exports
    }
  };

  vm.runInNewContext(fs.readFileSync(filePath), context);
  return context;
};

This is actually the code this post is all about :-D

Instead of using Node's require, we use loadModule function, which reads the content of requested module (javascript source file) and executes it on the context object. So all the private state of the module is dumped into the context object and yay, we can access everything! See vm.runInNewContext for more info.

Inside this context object, we defined our own require function, which means whenever the module asks for a dependency, our loadModule will be called intead of Node's require. That's pretty cool, because we can decide, whether we want to return a mock or real module, in which case we delegate the request to Node's require.

Very simple web server example

web-server.js
// this returns either real fs or mock fs (in test)
var fs = require('fs');
var http = require('http');

var MIME_TYPE = {
  txt: 'text/plain',
  html: 'text/html',
  js: 'application/javascript'
};

// we can access and test this function directly, without instantiating anything
var extensionFromUrl = function(url) {
  return url.split('.').pop(); //.replace(/\?.*$/, '');
};

var handleRequest = function(request, response) {
  fs.readFile(__dirname + request.url, function(error, data) {
    var contentType = MIME_TYPE[extensionFromUrl(request.url)] || MIME_TYPE.txt;
    response.setHeader('Content-Type', contentType);
    response.writeHead(error ? 404 : 200);
    response.end(data);
  });
};

// the public method, the only visible property of the module
exports.createServer = function() {
  return http.createServer(handleRequest);
};

Let's use it in test now

web-server.test.js
// Jasmine's syntax http://pivotal.github.com/jasmine/
describe('web-server', function() {
  // assuming we have mocks module :-D
  var mocks = require('./mocks');
  var loadModule = require('module-loader').loadModule;
  var module, fsMock, mockRequest, mockResponse;

  beforeEach(function() {
    fsMock = mocks.createFs();
    mockRequest = mocks.createRequest();
    mockResponse = mocks.createResponse();

    // load the module with mock fs instead of real fs
    // publish all the private state as an object
    module = loadModule('./web-server.js', {fs: fsMock});
  });

  it('extensionFromUrl() should parse basic extensions', function() {
    // we are testing private method of the module directly
    expect(module.extensionFromUrl('/some.html')).toBe('html');
    expect(module.extensionFromUrl('/another/file.js')).toBe('js');
    expect(module.extensionFromUrl('/file.with.more.dots.js')).toBe('js');
  });

  it('should return 200 if file exists', function() {
    // tell the mock, that this file exists
    fsMock.file('some/file.html');

    module.handleRequest(mockRequest, mockResponse);

    // wait for finishing the response, it's async
    waitsFor(function() {mockResponse.isFinished();});
    runs(function() {
      expect(mockResponse.status).toBe(200);
    });
  });
});

This is very simple example of unit testing web-server module, using loadModule function.

We can access both private functions as properties of module now, which is great, because we can add more tests very easily. For example, you might have noticed, that extensionFromUrl won't return correct extension when requested url contains query param. Piece of cake, just add a test that covers this bug:

it('extensionFromUrl() should ignore query params', function() {
  expect(module.extensionFromUrl('/some.html?param=ignored')).toBe('html');
});

The second test only asserts whether we set proper status code for existing file. We should assert status code for non existing file as well as caching headers, content type header and many other stuff. The important point here is, that it's fast, because it doesn't touch the real filesystem and still does test what needs to be tested - our code.


Viewing all articles
Browse latest Browse all 134

Trending Articles