UNPKG

esp-js-di

Version:

A tiny DI container (formally microdi-js)

980 lines (841 loc) 45.1 kB
/* notice_start * Copyright 2016 Dev Shop Limited * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. notice_end */ import microid from '../src/index'; describe('Container', () => { let container; beforeEach(() => { container = new microid.Container(); }); describe('.register()/.resolve() functionality', () => { it('should register/resolve an object with no dependencies', () => { let Foo = createObject({ bar: { value : 5 }}); container.register('foo', Foo); let foo = container.resolve('foo'); expect(foo.bar).toBeDefined(); expect(foo.bar).toBe(5); }); it('should register/resolve an object with a custom factory', () => { let Foo = createFunction({ bar: { value : 5 }}); container.registerFactory('foo', (context,...additionalDependencies) => new Foo(...additionalDependencies)); let foo = container.resolve('foo', 1, 2, 3); expect(foo.bar).toBeDefined(); expect(foo.bar).toBe(5); expect(foo.dependencies.length).toBe(3); }); it('should call init with resolving if object has init method', () => { let Foo = { init: function() { this._bar = 5; return this; }, get bar() { return this._bar; } }; container.register('foo', Foo); let foo = container.resolve('foo'); expect(foo.bar).toBeDefined(); expect(foo.bar).toBe(5); }); it('should register/resolve an object with string named dependencies', () => { let A = createObject(); let B = createObject(); let C = createObject(); container.register('a', A); container.register('b', B).inject('a'); container.register('c', C).inject('b'); let c = container.resolve('c'); expect(c.dependencies.length).toBe(1); expect(B.isPrototypeOf(c.dependencies[0])).toBe(true); }); it('should throw if circular dependency detected during .resolve()', () => { let A = createObject(); let B = createObject(); let C = createObject(); container.register('a', A).inject('b'); container.register('b', B).inject('a'); container.register('c', C).inject('b'); expect(() => { let b = container.resolve('b'); }).toThrow(); expect(() => { let a = container.resolve('a'); }).toThrow(); expect(() => { let c = container.resolve('c'); }).toThrow(); }); it('should register/resolve a given instance', () => { let instance = {}; container.registerInstance('a', instance); let resolved = container.resolve('a'); expect(resolved).toBe(instance); }); it('should throw if resolving an unregistered instance', () => { expect(() => { container.resolve('a'); }).toThrow(new Error('Nothing registered for dependency [a]')); }); it('should pass additional dependencies to object being resolved', () => { let A = createObject(); container.register('a', A); let resolved = container.resolve('a', "Foo", "Bar"); expect(resolved.dependencies.length).toEqual(2); expect(resolved.dependencies[0]).toEqual("Foo"); expect(resolved.dependencies[1]).toEqual("Bar"); }); describe('groups', () => { it('should be able able to register/resolve many objects with the same key', () => { let A = createObject(); let B = createObject(); container.register('a', A).inGroup('myGroup'); container.register('b', B).inject('a').inGroup('myGroup'); let group = container.resolveGroup('myGroup'); expect(A.isPrototypeOf(group[0])).toEqual(true); expect(B.isPrototypeOf(group[1])).toEqual(true); }); it('should throw if item already registered in group', () => { let A = createObject(); expect(() => { container .register('a', A) .inGroup('myGroup') .inGroup('myGroup'); }).toThrow(); }); it('should resolve group instances in the same order they were registered', () => { let N_OBJS = 5; for(let i=0; i<N_OBJS; i++){ let Obj = createObject({index: {value : i}}); container.register('object-' + i, Obj) .inGroup('foo'); } let objs = container.resolveGroup('foo'); expect(objs.length).toBe(N_OBJS); for(let i=0; i<N_OBJS; i++){ expect(objs[i].index).toBe(i); } }); it('should be able to inject group instances as an array', () => { let A = createObject(); let B = createObject(); container.register('a', A).inGroup('myGroup'); container.register('b', B).inGroup('myGroup'); let C = createObject(); container.register('c', C).inject('myGroup'); let a1 = container.resolve('a'); let b1 = container.resolve('b'); let c1 = container.resolve('c'); expect(c1.dependencies.length).toEqual(1); expect(c1.dependencies[0].length).toEqual(2); expect(c1.dependencies[0][0]).toBe(a1); expect(c1.dependencies[0][1]).toBe(b1); }); it('should respect the instance lifetime settings when resolving', () => { let A = createObject(); let B = createObject(); container.register('a', A) .transient() .inGroup('myGroup'); container.register('b', B) .inject('a') .singleton() .inGroup('myGroup'); let group1 = container.resolveGroup('myGroup'); let group2 = container.resolveGroup('myGroup'); expect(group1[0]).not.toBe(group2[0]); // registration 'a' is transient so we should get a new one each time expect(group1[1]).toBe(group2[1]); // registration 'b' is singleton so we should get the same one each time // the 'a' resolved as part of the group should be different as that given to 'b' as a dependency expect(A.isPrototypeOf(group1[0])).toBe(true); expect(A.isPrototypeOf(group1[1].dependencies[0])).toBe(true); expect(group1[0]).not.toBe(group1[1].dependencies[0]); }); it('should pass additional dependencies to object being resolved', () => { let A = createObject(); let B = createObject(); container.register('a', A) .transient() .inGroup('myGroup'); container.register('b', B) .transient() .inGroup('myGroup'); // if some registered items are singleton, then this will behave like `resolve`, those objects won't get recreated. let myGroup = container.resolveGroup('myGroup', "Foo", "Bar"); expect(myGroup[0].dependencies.length).toEqual(2); expect(myGroup[0].dependencies[0]).toEqual("Foo"); expect(myGroup[0].dependencies[1]).toEqual("Bar"); expect(myGroup[1].dependencies.length).toEqual(2); expect(myGroup[1].dependencies[0]).toEqual("Foo"); expect(myGroup[1].dependencies[1]).toEqual("Bar"); }); }); describe('constructor functions', () => { it('should register/resolve functions with new', () => { let Foo = createFunction(); let Bar = createFunction(); container.register('foo', Foo); container.register('bar', Bar).inject('foo'); let bar = container.resolve('bar'); let foo = container.resolve('foo'); expect(bar.dependencies[0]).toBe(foo); }); }); describe('dependency resolvers', () => { it('should register/resolve a dependency registered with a dependency key delegate resolver', () => { let A = createObject(); container.register('a', A).inject({ resolver: "delegate", resolve: function() { return { foo: 6 }; } }); let a = container.resolve('a'); expect(a.dependencies[0].foo).toBe(6); }); it('should register/resolve a dependency registered with delegate resolver', () => { let A = createObject(); container.register('a', { resolver: "delegate", resolve: () => { return Object.create(A); }, isResolverKey: true }); let a = container.resolve('a'); expect(A.isPrototypeOf(a)).toEqual(true); }); describe('auto factory dependency resolve', () => { let A, B, C, factoryForA, factoryForB; beforeEach(() => { A = createObject(); B = createObject(); C = createObject(); container.register('a', A); // singleton by default container.register('b', B).inject('a').transient(); container.register('c', C).inject({ resolver: "factory", key: 'a' // resolve 'a' each time the injected function is called }, { resolver: "factory", key: 'b' // resolve 'b' each time the injected function is called }); let c = container.resolve('c'); factoryForA = c.dependencies[0]; factoryForB = c.dependencies[1]; }); it('should resolve an instance each time the auto factory is invoked', () => { let b1 = factoryForB(), b2 = factoryForB(), b3 = factoryForB(); expect(B.isPrototypeOf(b1)).toEqual(true); expect(B.isPrototypeOf(b2)).toEqual(true); expect(B.isPrototypeOf(b3)).toEqual(true); expect(b1 != b2).toEqual(true); // transient registration so the instances should be different expect(b2 != b3).toEqual(true); }); it('should pass additional dependencies to the constructor on the instance being resolved', () => { let b1 = factoryForB("aParam", "anotherParam"); expect(b1.dependencies.length).toEqual(3); expect(A.isPrototypeOf(b1.dependencies[0])).toEqual(true); expect(b1.dependencies[1]).toEqual("aParam"); expect(b1.dependencies[2]).toEqual("anotherParam"); }); it('should throw if parameters passed and the resolve targetsingletonlton and already built', () => { let a1 = factoryForA("aParam", "anotherParam"); expect(() => { let a2 = factoryForA("aParam", "anotherParam"); }).toThrow(); }); }); it.skip('should pass container to dependency resolver', () => { }); it.skip('should throw if register called with unknown plugin', () => { }); it.skip('should resolve from custom resolvers', () => { }); }); describe('lifetime management', () => { it('should register/resolve a new instance each time if transiently registered', () => { let A = createObject(); container.register('a', A).transient(); let a1 = container.resolve('a'); let a2 = container.resolve('a'); expect(a1).not.toBe(a2); }); it('should register/resolve the same instance if registered as singleton', () => { let A = createObject(); container.register('a', A).singleton(); let a1 = container.resolve('a'); let a2 = container.resolve('a'); expect(a1).toBe(a2); }); }); }); describe('.createChildContainer()', () => { it.skip('should throw if called with arguments', () => { // pending }); describe('.register()/.resolve() ', () => { it('should default to resolving from parent when no configuration override exists in the child', () => { let A = createObject(); container.register('a', A); let childContainer = container.createChildContainer(); let a = childContainer.resolve('a'); expect(A.isPrototypeOf(a)).toBe(true); }); it('should register singleton instances with the container that owns the registration', () => { // this is an interesting edge case whereby if the root container owns the singleton registration // yet it's first resolved by a child, then the cached instance should belong in the parent let A = createObject(); container.register('a', A).singleton(); let childContainer = container.createChildContainer(); let c2a = childContainer.resolve('a'); let c1a = container.resolve('a'); expect(c2a).toBe(c1a); }); describe('groups', () => { let A, B; beforeEach(() => { A = createObject(); B = createObject(); container.register('a', A).singleton().inGroup('myGroup'); container.register('b', B).singleton().inGroup('myGroup'); }); it('should resolve group from parent when no configuration override exists in the child ', () => { let childContainer = container.createChildContainer(); let childGroup = childContainer.resolveGroup('myGroup'); let rootGroup = container.resolveGroup('myGroup'); // here the group resolved from the child should match the parent as // the group wasn't overridden in the child expect(childGroup[0]).toBe(rootGroup[0]); expect(childGroup[1]).toBe(rootGroup[1]); }); it('should resolve group from child when configuration override exists in the child', () => { let childContainer = container.createChildContainer(); // reconfigure the child container childContainer.register('a', A).singleton().inGroup('myGroup'); childContainer.register('b', B).singleton().inGroup('myGroup'); let childGroup = childContainer.resolveGroup('myGroup'); let rootGroup = container.resolveGroup('myGroup'); // the two groups should be different as the registration was overridden in the child expect(childGroup[0]).not.toBe(rootGroup[0]); expect(childGroup[1]).not.toBe(rootGroup[1]); }); }); }); describe('registration overrides', () => { it('should resolve from child when parent registration overridden', () => { let A = createObject(); let B = createObject(); container.register('a', A); container.register('b', B); let childContainer = container.createChildContainer(); // override 'a' configuration to make it take 'b' as a dependency. childContainer.register('a', A).inject('b'); let a1 = container.resolve('a'); let a2 = childContainer.resolve('a'); expect(a1).not.toBe(a2); expect(a1.dependencies.length).toBe(0); expect(a2.dependencies.length).toBe(1); }); it('should resolve a transient instance when a child container overrides a parents singleton registration', () => { let B = createObject(); container.register('b', B).singleton(); let b1 = container.resolve('b'); let childContainer = container.createChildContainer(); childContainer.register('b', B).transient(); let b2 = childContainer.resolve('b'); expect(b1).not.toBe(b2); let b3 = childContainer.resolve('b'); expect(b2).not.toBe(b3); let b4 = container.resolve('b'); expect(b1).toBe(b4); }); }); describe('lifetime management', () => { it('should register/resolve the same instance per container if registered as singletonPerContainer', () => { let A = createObject(); container.register('a', A).singletonPerContainer(); let childContainer = container.createChildContainer(); let a1 = container.resolve('a'); let a2 = container.resolve('a'); let b1 = childContainer.resolve('a'); let b2 = childContainer.resolve('a'); expect(a1).toBe(a2); expect(b1).toBe(b2); expect(a1).not.toBe(b1); }); }); describe('dependency resolvers', () => { it('should resolve from child dependency resolver when parent registration overridden', () => { let CustomResolver = { init: function(id) { this.id = id; return this; }, resolver: "myResolver", resolve: function(container) { return this.id; } }; let A = createObject(); container.addResolver("myResolver", Object.create(CustomResolver).init("container1Resolver")); container.register('a', A).inject({ resolver: "myResolver" }).singleton(); let childContainer = container.createChildContainer(); // replace the parent containers resolver childContainer.addResolver("myResolver", Object.create(CustomResolver).init("container2Resolver")); // you must also override the objects registration if it's a singleton otherwise // the container will just delegate to the root for resolution as that's whats owns the registration childContainer.register('a', A).inject({ resolver: "myResolver" }).singleton(); let a1 = container.resolve('a'); expect(a1.dependencies[0]).toBe("container1Resolver"); let a2 = childContainer.resolve('a'); expect(a2.dependencies[0]).toBe("container2Resolver"); }); }); describe('resolving objects that have injected containers from within child containers', () => { it('should return the same container that is resolving the object in question', () => { let A = createObject(); container.register('a', A) .transient() .inject(microid.EspDiConsts.owningContainer); let a1 = container.resolve('a'); expect(a1.dependencies[0]).toBe(container); let childContainer_1 = container.createChildContainer(); let a1_1 = childContainer_1.resolve('a'); expect(a1_1.dependencies[0]).toBe(childContainer_1); let a2 = container.resolve('a'); expect(a2.dependencies[0]).toBe(container); let childContainer_2 = container.createChildContainer(); let a1_2 = childContainer_2.resolve('a'); expect(a1_2.dependencies[0]).toBe(childContainer_2); }); }); }); describe('.dispose() container', () => { function createDisposable() { return createObject({ isDisposed: { get: function() { return this._isDisposed || false;}, set: function(value) { this._isDisposed = value; } }, dispose: { value: function() { this._isDisposed = true; }} }); } it('should dispose all singleton objects on container dispose', () => { let A = createDisposable(); container.register('a', A).singleton(); let a1 = container.resolve('a'); container.dispose(); expect(a1.isDisposed).toBe(true); }); it('should not dispose transient objects on container dispose', () => { let A = createDisposable(); container.register('a', A).transient(); let a1 = container.resolve('a'); container.dispose(); expect(a1.isDisposed).toBe(false); }); it('should not dispose external objects on container dispose', () => { let A = createDisposable(); // registerInstance has an external lifetime type container.registerInstance('a1', Object.create(A).init()); container.registerInstance('a2', Object.create(A).init(), true); let a1 = container.resolve('a1'); let a2 = container.resolve('a2'); container.dispose(); expect(a1.isDisposed).toBe(false); expect(a2.isDisposed).toBe(false); }); it('should dispose registered instance when their lifetime type was explicitly provided', () => { let A = createDisposable(); container.registerInstance('a', Object.create(A).init(), false); let a1 = container.resolve('a'); container.dispose(); expect(a1.isDisposed).toBe(true); }); it('should dispose singletonPerContainer instance only in the child container that was disposed', () => { let A = createDisposable(); container.register('a', A).singletonPerContainer(); let childContainer = container.createChildContainer(); let a = container.resolve('a'); let a1 = childContainer.resolve('a'); childContainer.dispose(); expect(a.isDisposed).toBe(false); expect(a1.isDisposed).toBe(true); }); it('should not resolve new instances once disposed', () => { let A = createDisposable(); container.register('a', A); container.dispose(); expect(() => { container.resolve('a'); }).toThrow(new Error("Container has been disposed")); }); it('should dispose child container on parent container dispose', () => { let A = createDisposable(); let B = createDisposable(); container.register('a', A); let childContainer = container.createChildContainer(); childContainer.register('b', B); let a = container.resolve('a'); let b = childContainer.resolve('b'); container.dispose(); expect(a.isDisposed).toBe(true); expect(b.isDisposed).toBe(true); }); it('should not resolve new instance from child once parent is disposed', () => { let A = createDisposable(); let childContainer = container.createChildContainer(); childContainer.register('a', A); container.dispose(); expect(() => { childContainer.resolve('a'); }).toThrow(new Error("Container has been disposed")); }); it('should not dispose parent on child container dispose', () => { let A = createDisposable(); let B = createDisposable(); container.register('a', A); let childContainer = container.createChildContainer(); childContainer.register('b', B); let a = container.resolve('a'); let b = childContainer.resolve('b'); childContainer.dispose(); expect(a.isDisposed).toBe(false); expect(b.isDisposed).toBe(true); }); it('should still be able to resolve from parent on child container dispose', () => { let A = createDisposable(); container.register('a', A).singletonPerContainer(); let childContainer = container.createChildContainer(); let a = container.resolve('a'); let a1 = childContainer.resolve('a'); childContainer.dispose(); expect(a.isDisposed).toBe(false); expect(a1.isDisposed).toBe(true); let a2 = container.resolve('a'); expect(a2.isDisposed).toBe(false); }); }); describe('isRegistered', () => { it('Should return true when a key is registered', () => { container.register('a', {}).singleton(); let isRegistered = container.isRegistered('a'); expect(isRegistered).toBe(true); }); it('Should return false when a key is not registered', () => { container.register('a', {}).singleton(); let isRegistered = container.isRegistered('b'); expect(isRegistered).toBe(false); }); describe('isRegistered(key, IsRegisteredQueryOptions)', () => { let childContainer; describe('when item is registered in parent', () => { beforeEach(() => { container.register('a', {}); childContainer = container.createChildContainer(); }); it('Should return true by default', () => { let isRegistered = childContainer.isRegistered('a'); expect(isRegistered).toBe(true); }); it('Should return true when searchParentContainers is true', () => { let isRegistered = childContainer.isRegistered('a', { searchParentContainers: true }); expect(isRegistered).toBe(true); }); it('Should return false when searchParentContainers is false', () => { let isRegistered = childContainer.isRegistered('a', { searchParentContainers: false }); expect(isRegistered).toBe(false); }); }); describe('when item is registered in child', () => { beforeEach(() => { childContainer = container.createChildContainer(); childContainer.register('a', {}); }); it('Should return true by default', () => { let isRegistered = childContainer.isRegistered('a'); expect(isRegistered).toBe(true); }); it('Should return true when searchParentContainers is true', () => { let isRegistered = childContainer.isRegistered('a', { searchParentContainers: true }); expect(isRegistered).toBe(true); }); it('Should return true when searchParentContainers is false', () => { let isRegistered = childContainer.isRegistered('a', { searchParentContainers: false }); expect(isRegistered).toBe(true); }); }); }); }) describe('isGroupRegistered', () => { it('Should return true when a key is registered in group', () => { container.register('a', {}).inGroup('group-1'); container.register('b', {}).inGroup('group-1'); let isGroupRegistered = container.isGroupRegistered('group-1'); expect(isGroupRegistered).toBe(true); }); it('Should return false when a key is not registered in group', () => { container.register('a', {}).singleton(); let isGroupRegistered = container.isGroupRegistered('b'); expect(isGroupRegistered).toBe(false); }); describe('isGroupRegistered(key, IsRegisteredQueryOptions)', () => { let childContainer; describe('when item is registered in parent', () => { beforeEach(() => { container.register('a', {}).inGroup('group-1'); childContainer = container.createChildContainer(); }); it('Should return true by default', () => { let isGroupRegistered = childContainer.isGroupRegistered('group-1'); expect(isGroupRegistered).toBe(true); }); it('Should return true when searchParentContainers is true', () => { let isGroupRegistered = childContainer.isGroupRegistered('group-1', { searchParentContainers: true }); expect(isGroupRegistered).toBe(true); }); it('Should return false when searchParentContainers is false', () => { let isGroupRegistered = childContainer.isGroupRegistered('group-1', { searchParentContainers: false }); expect(isGroupRegistered).toBe(false); }); }); describe('when item is registered in child', () => { beforeEach(() => { childContainer = container.createChildContainer(); childContainer.register('a', {}).inGroup('group-1'); }); it('Should return true by default', () => { let isGroupRegistered = childContainer.isGroupRegistered('group-1'); expect(isGroupRegistered).toBe(true); }); it('Should return true when searchParentContainers is true', () => { let isGroupRegistered = childContainer.isGroupRegistered('group-1', { searchParentContainers: true }); expect(isGroupRegistered).toBe(true); }); it('Should return true when searchParentContainers is false', () => { let isGroupRegistered = childContainer.isGroupRegistered('group-1', { searchParentContainers: false }); expect(isGroupRegistered).toBe(true); }); }); }); }) describe('container events', () => { let notifications1 = []; let notifications2 = []; const callback1 = (notification) => { notifications1.push(notification); }; const callback2 = (notification) => { notifications2.push(notification); }; beforeEach(() => { notifications1 = []; notifications2 = []; }); it('on(instanceRegistered, callback): callback invoked when instance registered', () => { container.on('instanceRegistered', callback1); let instance = new Object(); container.registerInstance('foo', instance); expect(notifications1.length).toEqual(1); expect(notifications1[0].name).toEqual('foo'); expect(notifications1[0].instance).toBe(instance); expect(notifications1[0].eventType).toEqual('instanceRegistered'); }); it('off(instanceRegistered, callback): removes callback', () => { container.on('instanceRegistered', callback1); container.off('instanceRegistered', callback1); let instance = new Object(); container.registerInstance('foo', instance); expect(notifications1.length).toEqual(0); }); it('on(instanceRegistered, callback): multiple handlers accepted', () => { let Foo = createFunction(); let Bar = createFunction(); let Baz = createFunction(); let Bop = createFunction(); container.registerInstance('foo', Foo); expect(notifications1.length).toEqual(0); expect(notifications2.length).toEqual(0); container.on('instanceRegistered', callback1); container.registerInstance('bar', Bar); expect(notifications1.length).toEqual(1); expect(notifications2.length).toEqual(0); container.on('instanceRegistered', callback2); container.registerInstance('baz', Baz); expect(notifications1.length).toEqual(2); expect(notifications2.length).toEqual(1); container.off('instanceRegistered', callback1); container.off('instanceRegistered', callback2); container.registerInstance('bop', Bop); expect(notifications1.length).toEqual(2); expect(notifications2.length).toEqual(1); }); it('on(instanceCreated, callback): callback invoked when instance resolved', () => { container.on('instanceCreated', callback1); let Foo = createFunction(); container.register('foo', Foo); // singleton by default let foo = container.resolve('foo'); expect(notifications1.length).toEqual(1); expect(notifications1[0].name).toEqual('foo'); expect(notifications1[0].instance).toBe(foo); expect(notifications1[0].eventType).toEqual('instanceCreated'); container.resolve('foo'); expect(notifications1.length).toEqual(1); }); it('off(instanceCreated, callback): removes callback', () => { container.on('instanceCreated', callback1); container.off('instanceCreated', callback1); let Foo = createFunction(); container.register('foo', Foo); container.resolve('foo'); expect(notifications1.length).toEqual(0); }); it('on(instanceCreated, callback): callback invoked per transient resolution', () => { container.on('instanceCreated', callback1); let Foo = createFunction(); container.register('foo', Foo).transient(); container.resolve('foo'); container.resolve('foo'); container.resolve('foo'); expect(notifications1.length).toEqual(3); }); it('on(instanceCreated, callback): multiple handlers accepted', () => { let Foo = createFunction(); container.register('foo', Foo).transient(); container.on('instanceCreated', callback1); container.on('instanceCreated', callback2); container.resolve('foo'); container.resolve('foo'); expect(notifications1.length).toEqual(2); expect(notifications2.length).toEqual(2); container.off('instanceCreated', callback2); container.resolve('foo'); expect(notifications1.length).toEqual(3); expect(notifications2.length).toEqual(2); container.off('instanceCreated', callback1); container.resolve('foo'); expect(notifications1.length).toEqual(3); expect(notifications2.length).toEqual(2); }); }) describe('incorrect argument handling', () => { it('throws if arguments incorrect for .register()', () => { let keyError = new Error('EspDi: Error calling register(name, proto). The name argument must be a string and can not be \'\''); expect(() => {container.register(); }).toThrow(keyError); expect(() => {container.register(undefined, {}); }).toThrow(keyError); expect(() => {container.register('', {}); }).toThrow(keyError); expect(() => {container.register(null, {}); }).toThrow(keyError); expect(() => {container.register({}, {}); }).toThrow(keyError); let registrationError = new Error('EspDi: Error calling register(name, proto). Registered item for [foo] can not be null or undefined'); expect(() => {container.register('foo', undefined); }).toThrow(registrationError); expect(() => {container.register('foo', null); }).toThrow(registrationError); expect(() => {container.register('foo', 1); }).toThrow(new Error('EspDi: Error calling register(name, proto). Can not register a number instance against key [foo], use registerInstance(name, instance)')); expect(() => {container.register('foo', 'aString'); }).toThrow(new Error('EspDi: Error calling register(name, proto). Can not register a string instance against key [foo], use registerInstance(name, instance)')); }); it('should not throw if .register() called with existing registration key', () => { // you should be allowed to override a registration let A = createObject(); container.register('foo', A); let B = createObject(); container.register('foo', B); let b = container.resolve('foo'); expect(B.isPrototypeOf(b)).toEqual(true); }); it('throws if arguments incorrect for .registerInstance()', () => { let keyError = new Error('EspDi: Error calling register(name, instance, isExternallyOwned = true). The name argument must be a string and can not be \'\''); expect(() => {container.registerInstance(); }).toThrow(keyError); expect(() => {container.registerInstance(undefined, {}); }).toThrow(keyError); expect(() => {container.registerInstance('', {}); }).toThrow(keyError); expect(() => {container.registerInstance(null, {}); }).toThrow(keyError); expect(() => {container.registerInstance({}, {}); }).toThrow(keyError); let registrationError = new Error('EspDi: Error calling registerInstance(name, instance, isExternallyOwned = true). Provided instance for [foo] can not be null or undefined'); expect(() => {container.registerInstance('foo', undefined); }).toThrow(registrationError); expect(() => {container.registerInstance('foo', null); }).toThrow(registrationError); }); it('throws if arguments incorrect for .resolve()', () => { let keyError = new Error('EspDi: Error calling resolve(name, ...additionalDependencies). The name argument must be a string and can not be \'\''); expect(() => {container.resolve(); }).toThrow(keyError); expect(() => {container.resolve(undefined); }).toThrow(keyError); expect(() => {container.resolve(''); }).toThrow(keyError); expect(() => {container.resolve(null); }).toThrow(keyError); expect(() => {container.resolve({}, {}); }).toThrow(keyError); }); it('throws if arguments incorrect for .resolveGroup()', () => { let keyError = new Error('EspDi: Error calling resolveGroup(groupName). The groupName argument must be a string and can not be \'\''); expect(() => {container.resolveGroup(); }).toThrow(keyError); expect(() => {container.resolveGroup(undefined); }).toThrow(keyError); expect(() => {container.resolveGroup(''); }).toThrow(keyError); expect(() => {container.resolveGroup(null); }).toThrow(keyError); expect(() => {container.resolveGroup({}); }).toThrow(keyError); }); it('throws if arguments incorrect for .addResolver()', () => { let resolver = () => {}; let keyError = new Error('EspDi: Error calling addResolver(name, resolver). The name argument must be a string and can not be \'\''); expect(() => {container.addResolver(); }).toThrow(keyError); expect(() => {container.addResolver(undefined); }).toThrow(keyError); expect(() => {container.addResolver(''); }).toThrow(keyError); expect(() => {container.addResolver(null); }).toThrow(keyError); expect(() => {container.addResolver({}); }).toThrow(keyError); let registrationError = new Error('EspDi: Error calling addResolver(name, resolver). Provided resolver for [foo] can not be null or undefined'); expect(() => {container.addResolver('foo'); }).toThrow(registrationError); expect(() => {container.addResolver('foo', undefined); }).toThrow(registrationError); expect(() => {container.addResolver('foo', null); }).toThrow(registrationError); }); it('throws if arguments incorrect for .isRegistered()', () => { let keyError = new Error('EspDi: Error calling isRegistered(name). The name argument must be a string and can not be \'\''); expect(() => {container.isRegistered(); }).toThrow(keyError); expect(() => {container.isRegistered(undefined); }).toThrow(keyError); expect(() => {container.isRegistered(''); }).toThrow(keyError); expect(() => {container.isRegistered(null); }).toThrow(keyError); expect(() => {container.isRegistered({}); }).toThrow(keyError); }); it('throws if arguments incorrect for .isGroupRegistered()', () => { let keyError = new Error('EspDi: Error calling isGroupRegistered(groupName). The groupName argument must be a string and can not be \'\''); expect(() => {container.isGroupRegistered(); }).toThrow(keyError); expect(() => {container.isGroupRegistered(undefined); }).toThrow(keyError); expect(() => {container.isGroupRegistered(''); }).toThrow(keyError); expect(() => {container.isGroupRegistered(null); }).toThrow(keyError); expect(() => {container.isGroupRegistered({}); }).toThrow(keyError); }); it('should throw if .inGroup() called with incorrect arguments', () => { let inGroupKeyError = new Error('EspDi: Error calling inGroup(groupName). The name argument must be a string and can not be \'\''); expect(() => { container.register('foo', {}).inGroup(); }).toThrow(inGroupKeyError); expect(() => { container.register('foo', {}).inGroup(undefined); }).toThrow(inGroupKeyError); expect(() => { container.register('foo', {}).inGroup(''); }).toThrow(inGroupKeyError); expect(() => { container.register('foo', {}).inGroup(null); }).toThrow(inGroupKeyError); expect(() => { container.register('foo', {}).inGroup({}); }).toThrow(inGroupKeyError); }); }); function createObject(props) { let o = Object.create(Object.prototype, { // The container will call any init() if found. // Not commonly used these days as containers are often used for classes init : { value: function() { this.dependencies = Array.prototype.slice.call(arguments); return this; } } } ); if(props !== undefined) Object.defineProperties(o, props); return o; } function createFunction(props) { function AFunction(...dependencies) { this.dependencies = dependencies; } AFunction.prototype = createObject(props); return AFunction; } });