Skip to content

Instantly share code, notes, and snippets.

@tizzo
Created April 8, 2019 18:07
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save tizzo/bcbe56f6ff0e599a835ef6bde08b8cb6 to your computer and use it in GitHub Desktop.
Save tizzo/bcbe56f6ff0e599a835ef6bde08b8cb6 to your computer and use it in GitHub Desktop.
'use strict';
const should = require('should');
const DiC = require('../../lib/DependencyInjectionContainer');
/**
* @class Service class test fixture.
*/
class Foo {
constructor() {
this.custructorArgs = arguments;
}
setValue(value) {
this.value = value;
}
getValue() {
return this.value;
}
bar() {
return 'baz';
}
}
describe('DependencyInjectionContainer', () => {
describe('service registration', () => {
it('should throw if you try to access a raw unregistered service', () => {
const c = new DiC();
should.throws(() => c.getRaw('foo'), Error);
});
it('should throw if you try to instantiate an unregistered service', () => {
const c = new DiC();
should.throws(() => c.get('foo'), Error);
});
it('should be able to register and instantiate a simple service', () => {
const c = new DiC();
c.registerService('foo', Foo);
c.get('foo').bar().should.equal('baz');
});
it('should allow a service to specify static arguments', () => {
const c = new DiC();
c.registerService('foo', Foo)
.registerArgument('foo')
.registerArgument('bar');
c.get('foo').custructorArgs[0].should.equal('foo');
c.get('foo').custructorArgs[1].should.equal('bar');
});
it('should allow a service to specify dynamic arguments', () => {
const c = new DiC();
c.registerService('foo', Foo)
.registerArgument((container) => 'foo')
.registerArgument('bar');
c.get('foo').custructorArgs[0].should.equal('foo');
c.get('foo').custructorArgs[1].should.equal('bar');
});
it('should allow services to register properties that must be set', () => {
const c = new DiC();
c.registerService('foo', Foo)
.addSetter('setValue', 'blah');
c.get('foo').getValue().should.equal('blah');
});
it('should support setting simple properties', () => {
const c = new DiC();
c.registerService('foo', Foo)
.addProperty('someProperty', 'darth vader');
c.get('foo').someProperty.should.equal('darth vader');
});
it('should support setting dynamic properties', () => {
const c = new DiC();
c.registerService('foo', Foo)
.addProperty('someProperty', () => 'darth vader');
c.get('foo').someProperty.should.equal('darth vader');
});
it('should allow one service to inherit from the definition of another', () => {
const c = new DiC();
c.registerService('foo', Foo)
.addProperty('name', 'Darth');
c.registerService('foo_child', Foo)
.inherits('foo')
.addProperty('surname', 'Vader');
c.get('foo_child').name.should.equal('Darth');
c.get('foo_child').surname.should.equal('Vader');
});
it('should support chained inheritance', () => {
const c = new DiC();
c.registerService('foo', Foo)
.addProperty('name', 'Darth');
c.registerService('foo_child', Foo)
.inherits('foo')
.addProperty('surname', 'Vader');
c.registerService('foo_grandchild', Foo)
.inherits('foo_child')
.addProperty('son', 'Luke');
c.get('foo_grandchild').name.should.equal('Darth');
c.get('foo_grandchild').surname.should.equal('Vader');
c.get('foo_grandchild').son.should.equal('Luke');
});
it('should allow convenience property syntax for fetching serices', () => {
const c = new DiC();
c.registerService('foo', Foo)
.addProperty('name', 'Darth');
c.foo.name.should.equal('Darth');
});
});
describe('value registration', () => {
it('should allow a value to be registered', () => {
const c = new DiC();
c.registerValue('foo', {bar: 'baz'});
c.foo.bar.should.equal('baz');
});
});
describe('value protection', () => {
it('should prevent the execution of a registered function', () => {
const c = new DiC();
c.registerService('foo', Foo)
.addProperty('someProperty', c.protect(() => 'darth vader'));
c.foo.someProperty().should.equal('darth vader');
});
});
});
'use strict';
const Service = require('./Service');
const _services = Symbol('_services');
const _values = Symbol('_values');
/**
* @class A simple dependency injection container for lazy loading and instantiating services.
*/
class DependencyInjectionContainer {
constructor() {
this[_services] = {};
this[_values] = {};
}
/**
* Registers a value in the container.
*
* @param {string} name - The name of this value
* @param {mixed} value - The value to be registered within the container.
*/
registerValue(name, value) {
this[_values][name] = value
Object.defineProperty(this, name, {
get: this.get.bind(this, name),
});
return this[_values][name];
}
/**
* Registers a service in the container.
*
* @param {string} name - The name of this service
* @param {Class} ServiceClass - The class that will be instantiated when loading this class.
* @param {Object} options - An options object passed through to the Service constructor.
*/
registerService(name, ServiceClass, options = {}) {
this[_services][name] = new Service(name, ServiceClass, options);
Object.defineProperty(this, name, {
get: this.get.bind(this, name),
});
return this[_services][name];
}
/**
* Get an instantiated service or value.
*
* @param {String} name - The name of the service or value to instantiate.
* @return {mixed} - The fully instantiated service.
*/
get(name) {
if (this[_services][name]) {
return this.getRaw(name).get(this);
}
else if(this[_values][name]) {
return this[_values][name];
}
throw new Error(`No service or value registered for ${name}`);
}
/**
* Wraps a callable function to ensure that it is not executed when fetched.
*
* This is useful for wrapping functions that need to be injected into services.
*
* @param {mixed} value - A parameter to wrap in a function
* @return {Function} - An anonymous function that returns the provided value.
*/
protect(value) {
return function() {
return value;
};
}
/**
* Retrieves the service object rather than the instantiated service.
*
* @param {string} name - The name of the service object to return.
* @return {Service} - The Service object.
*/
getRaw(name) {
if (this[_services][name] === undefined && this[_values][name] === undefined) {
throw new Error(`The service ${name} has not been defined`);
}
return this[_services][name];
}
}
module.exports = DependencyInjectionContainer;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment