Skip to content
Patrick Wolf edited this page Feb 4, 2016 · 1 revision

Testing Services

Services rely on dependency injection for initialization, which helps when it comes to unit testing. It's fairly simple to mock out the dependencies that a service relies on. There are a few ways this can be done.

Manually calling init

This is the simplest way to initialize a service for injection, although it's fragile because it requires specifying init parameters in the correct order.

As a simple example, let's create a calculator service for performing simple arithmetic operations. Our calculator service has a configurable base, which is retrieved from the config service.

//services/calculator.js
var base = null;

exports.init = function(config) {
    var cfg = config.get('calculator');
    base = cfg.base || 10;
}

exports.add = function(num1str, num2str) {
    var num1 = parseInt(num1str, base);
    var num2 = parseInt(num2str, base);
    return (num1 + num2).toString(base);
}

In order to test the service, we need to require the module and initialize it.

var calcService = require('./services/calculator');
calcService.init();

However, the call to init will fail because it's expecting a reference to the config service. The only thing the calculator service does with the config service is to call config.get('calculator'), so we could easily create a mock object to do the same.

With sinon:

var sinon = require('sinon');
var config = {};
config.get = sinon.stub();
config.get.withArgs('calculator').returns({base: 16});

Without sinon:

var config = {
   get: function(key) {
     if (key === 'calculator') return {base: 16}
   }
}

Using the test utility

As mentioned earlier, manually calling init can be fragile since it makes assumptions about which parameters an init function needs, and whether it's asynchronous. It also requires mocking some of the built-in services, like config and logger.

A better approach is to use a test utility provided by BlueOak Server in order to initialize your services.

var server = require('blueoak-server');
var testUtil = server.testUtility();

var calcService = require('./services/calculator');
var cfg = {
  calculator: {
    base: 16
  }
};
testUtil.initService(calcService, cfg);

The call to initService will look at the init method of the given service module and inject the dependencies. The config and logger service are automatically handled. And as an added convenience, the json data to populate the config service can be specified.

Injecting dependencies

Suppose our service has dependencies on more than just config and logger. We need to be able to pass in mock versions of other dependencies as well.

Those too can be passed into the initService call.

var deps = {
  myService: {
    //this would contain mock functions
  }
}
testUtil.initService(calcService, cfg, deps);

This time if the the calculator service depended on myService, it would automatically be passed into the init call.

Asynchronous Services

Services will often be asynchronous, i.e. have a callback in the init function.

exports.init = function(config, logger, callback) {
   // do some initialization
   callback();
}

We can pass a callback to initService to be called once the service is initialized. This works regardless of whether a service actually uses a callback, so it's a good practice to always use this method so that you don't have to make any assumptions about whether a service really is asynchronous.

testUtil.initService(calcService, cfg, deps, function(err) {
  if (!err) {
    console.log("Finished initializing service!");
  }
});
Clone this wiki locally