Dependency injection

Unit.js is built on top of a container IOC (Inversion of Control), based on Noder.io.

Know the lib Noder.io is not required to be comfortable with Unit.js. However, I advise you to check the API doc of Noder.io because the IOC will bring many benefits to write and organize your tests in a simple way and flexible.

Example using dependency injection to reuse a feature.

var test = require('unit.js');

test.$factory('httpRequest', function() {
  return test.promisifyAll(require('request'));
});

describe('HTTP promise', function() {

  it('should connect to Google', function(done) {
    var request = test.$di.get('httpRequest');

    request.getAsync('http://google.com')
      .spread(function (res, body) {
        test
          .number(res.statusCode)
            .is(200)

          .string(body)
            .contains('google')
        ;
      })
      .catch(function (err) {
        test.fail(err.message);
      })
      .finally(done)
      .done();
  });

  it('should get API', function(done) {
    var request = test.$di.get('httpRequest');

    request.getAsync('http://localhost:3000/api/something')
      .spread(function (res, body) {
        // your tests ...
      })
      .catch(function (err) {
        test.fail(err.message);
      })
      .finally(done)
      .done()
  });
});

Other example, make a reusable helper for use in multiple files of unit tests.
Create 2 new assertions in the IOC container:

var test = require('unit.js');

test

  .$provider('isUser', 'User', function(User) {

    return function isUser(user) {
      test
        .object(user)
          .isInstanceOf(User)
          .hasProperty('role')
      ;

      return test;
    };
  })

  .$provider('isEmployee', 'isUser', function(isUser) {

    return function isEmployee(user) {

      isUser(user).string(user.role)
        .match(/staff|sales|engineer|manager/);

      return test;
    };
  })

  .$di.set('isManager', function IsManager(user) {

    this
      .isUser(user)

      .string(user.role)
        .isIdenticalTo('manager')
    ;

    return test;
  });
;

Now you can use anywhere this helper:

var vanessa = new Employee('vanessa');
var nico    = new Employee('nico');

test

  // a way to get a provider (and other items from the IOC container)
  .$invoke('isEmployee', function(isEmployee) {

    return isEmployee(vanessa).isEmployee(nico);
  }

  .date(vanessa.vacancy)
    .isAfter(nico.vacancy)

  .number(vanessa.salary)
    .isIdenticalTo(nico.salary)

  .string(vanessa.role)
    .isIdenticalTo(nico.role)
    .isIdenticalTo('engineer')

  .if('change the role', vanessa.role = 'boss')
    .error(function() {

      // another way to get a provider (and other items from the IOC container)
      var isEmployee = test.$di.get('isEmployee');

      // fail because "boss" is not a valid role
      isEmployee(vanessa);
    })

  .when(vanessa.role = 'manager')

  // another way to get a provider (and other items from the IOC container)
  .then(function() {

    // "this" bind the container

    // passes because "manager" is a valid role
    this
      .isEmployee(vanessa)

      .number(vanessa.salary)
        .isGreaterThan(nico.salary)
    ;
  })

  // another way to get a provider (and other items from the IOC container)
  .$apply(function() {

    // "this" bind the container

    // passes because "vanessa" is a valid user and "manager" is its new role
    return this.isManager(vanessa);
  })
;

This unit tests series is explicit and we can add lots of helpers which makes easy the life. Unit.js provides several ways to recover the items placed in the container, you choose your favorite way.

We have seen a short example of IOC possibilities, but notes there are many other useful features related to IOC in Unit.js.

My preferred way to get items added in the IOC container, is to use the scope (this) of given(), when(), then(), case(), wait().

Also, sometimes I use directly test.$di.set('anyItems', 'any value'), test.$di.raw('anyItems') or test.$di.get('anyItem'). Depending on the context.

These methods binds the IOC container to the scope (this):

test.$di.set('sayHello', function(name) {
  return 'Hello ' + name;
});

test
  .case(function() {

    // returns: 'Hello Nico'
    this.sayHello('Nico');
  })

  .if(function() {

    // returns: 'Hello Nico'
    this.sayHello('Nico');
  })

  .and(function() {

    // returns: 'Hello Nico'
    this.sayHello('Nico');
  })

  .given(function() {

    // returns: 'Hello Nico'
    this.sayHello('Nico');
  })

  .when(function() {

    // returns: 'Hello Nico'
    this.sayHello('Nico');
  })

  .then(function() {

    // returns: 'Hello Nico'
    this.sayHello('Nico');
  })

  .wait(20, function() {

    // returns: 'Hello Nico'
    this.sayHello('Nico');
  })

  .exception(function() {

    // returns: 'Hello Nico'
    this.sayHello('Nico');
  })

  .error(function() {

    // returns: 'Hello Nico'
    this.sayHello('Nico');
  })
;

Protip

For bundled the code to re-use easily across multiple projects or for a large application with its specific modules, it may be useful to create a special plugin to facilitate testing of each module (module initialization, handle the database via the ORM used by the application, test the controllers, ...).