Partial dependency injection

Examples of dependency injection in javascript

Let's take a function with 3 arguments that computes and logs a sum of two numbers

1
2
3
function logSum(a, b, log) {
log(a + b);
}

Passing a logger function as a log argument makes unit testing logSum very easy

1
2
3
4
5
6
QUnit.test('log sum', function (assert) {
var logger = function (value) {
assert.equal(value, 10, 'logging correct value');
};
logSum(logger, 7, 3);
});

Dependency injection #

This pattern is called dependency injection - providing dynamic arguments to the function, widely used by AngularJs for example. Dependency injection has a run time executor which inspects a function and injects correct argument by name. Or it could be done via proxy function, like in heroin

1
2
3
4
5
6
7
8
9
10
11
12
QUnit.test('log sum', function (assert) {
var logger = function (value) {
assert.equal(value, 10, 'logging correct value');
};
var logSumProxy = heroin(logSum, {
log: logger,
a: 7,
b: 3
});
logSumProxy();
// each argument is injected
});

What if we want to bind some arguments to the function, but leave others unchanged?

Partial argument binding #

We could use Function.bind binding arguments to the function in left to right order by position:

1
2
3
4
5
6
7
8
9
QUnit.test('log sum', function (assert) {
var logger = function (value) {
assert.ok(value, 'logging some value');
};
var logSumProxy = logSum.bind(null, logger);
logSumProxy(7, 3);
logSumProxy(-1, 1);
logSumProxy(100, 1);
});

Notice we performed partial binding - only providing the first argument, leaving 2nd and 3rd arguments free. Can we do the same for dependency injection?

Partial dependency injection #

Yes, using heroin we can. If the dependency object does not contain an own property for an argument, it is left free and you can provide it during the execution

1
2
3
4
5
6
7
8
9
QUnit.test('log sum', function (assert) {
var logger = function (value) {
assert.equal(value, 7, 'logging correct value');
};
var logSumProxy = heroin(logSum, {
log: logger
});
logSumProxy(3, 4);
});

The cool thing is that argument for the dependency injection does not matter, we could inject log and b, leaving a in the middle free

1
2
3
4
5
6
7
8
9
10
11
QUnit.test('log sum', function (assert) {
var logger = function (value) {
assert.equal(value, 13, 'logging correct value');
};
var logSumProxy = heroin(logSum, {
log: logger,
b: 10
});
// logSumProxy(log = logger, a, b = 10) ...
logSumProxy(3);
});

Use case #

A good use case for partial dependency injection is in my qunit-inject plugin. It allows one to inject properties from a module into the individual unit tests, while still leaving argument assert free to be provided by the runtime framework

1
2
3
4
5
6
7
8
QUnit.module('QUnit.assert tests WITH injection', {
a: 42,
b: 1
});
QUnit.test('injection sandwich', function (b, assert, a) {
assert.equal(a, 42, 'assert works');
assert.equal(b, 1, 'b value');
});

qunit-inject creates a proxy for each unit test function, injecting module's config, but since the config does not have assert property, the QUnit's runner sees only a proxy that expects assert argument. And if you are terribly evil, you can even inject your own assert from the module, so that QUnit's assert argument is ignored.

1
2
3
4
5
6
7
8
9
10
11
12
QUnit.module('Your assert', {
assert: {
ok: function (condition, msg) {
console.log('whatever');
}
},
a: 42
});
QUnit.test('check a', function (assert, a) {
assert.ok(a);
});
// prints 'whatever'