Browse Source

More unit tests and code coverage

singleton
garrettmills 1 year ago
parent
commit
a5fb09e99e
No known key found for this signature in database GPG Key ID: 6ACD58D6ADACFC6E
  1. 1
      .gitignore
  2. 4
      package.json
  3. 95
      test/Container.spec.js
  4. 151
      test/DependencyInjector.spec.js
  5. 31
      test/Injectable.spec.js
  6. 4
      test/integration/Integration.spec.js
  7. 20
      test/integration/Singleton.spec.js
  8. 801
      yarn.lock

1
.gitignore

@ -1,3 +1,4 @@
.idea*
.env
node_modules*
.nyc_output*

4
package.json

@ -9,10 +9,12 @@
"dependencies": {
"chai": "^4.2.0",
"mocha": "^7.0.1",
"nyc": "^15.0.1",
"sinon": "^8.1.1"
},
"scripts": {
"test_units": "./node_modules/.bin/mocha --reporter spec test",
"test_integration": "./node_modules/.bin/mocha --reporter spec test/integration"
"test_integration": "./node_modules/.bin/mocha --reporter spec test/integration",
"test_coverage": "./node_modules/.bin/nyc ./node_modules/.bin/mocha --reporter spec test"
}
}

95
test/Container.spec.js

@ -13,10 +13,13 @@ describe('the inversion of control container', function() {
})
it('should accept definitions as parameters on construction', function() {
const example_defs = {}
class Test {}
const example_defs = {
foo: Test
}
const container = new Container(example_defs)
expect(container.definitions).to.be.equal(example_defs)
expect(container.definitions.foo).to.exist
})
it('should set an empty object of definitions by default', function() {
@ -66,21 +69,42 @@ describe('the inversion of control container', function() {
})
})
describe('service function', function() {
describe('[DEPRECATED] service function', function() {
it('should be a function', function() {
const container = new Container()
expect(container.service).to.be.a('function')
})
it('should require no arguments', function() {
it('should require one argument', function() {
const container = new Container()
expect(container.service.length).to.be.equal(0)
expect(container.service.length).to.be.equal(1)
})
it('should call the non-deprecated get method', function() {
const container = new Container()
container.get = sinon.spy()
const sym = Symbol()
container.service(sym)
expect(container.get.calledOnce).to.be.true
expect(container.get.getCall(0).args[0]).to.be.equal(sym)
})
})
describe('get function', function() {
it('should be a function', function() {
const container = new Container()
expect(container.get).to.be.a('function')
})
it('should require one argument', function() {
const container = new Container()
expect(container.get.length).to.be.equal(1)
})
it('should throw an error with a helpful message if the passed in service has no definition', function() {
const container = new Container()
const name = Math.random().toString(36).substring(7)
expect(container.service.bind(container, name)).to.throw('No such service registered with this container: '+name)
expect(container.get.bind(container, name)).to.throw(`${name}`)
})
it('should instantiate service classes by name', function() {
@ -89,7 +113,7 @@ describe('the inversion of control container', function() {
const defs = {}
defs[name] = TestService
const container = new Container(defs)
expect(container.service(name)).to.be.an.instanceof(TestService)
expect(container.get(name)).to.be.an.instanceof(TestService)
})
it('should re-use instantiated services', function() {
@ -98,8 +122,8 @@ describe('the inversion of control container', function() {
const defs = {}
defs[name] = TestService
const container = new Container(defs)
const test_service_1 = container.service(name)
const test_service_2 = container.service(name)
const test_service_1 = container.get(name)
const test_service_2 = container.get(name)
expect(test_service_1).to.be.equal(test_service_2)
})
@ -109,24 +133,12 @@ describe('the inversion of control container', function() {
const defs = { test: TestService }
const container = new Container(defs)
container.di = fake_di
container.service('test')
container.get('test')
expect(fake_di.make.calledOnce).to.be.true
expect(fake_di.make.getCall(0).args.length).to.be.equal(1)
expect(fake_di.make.getCall(0).args[0]).to.be.equal(TestService)
})
it('should return a proxy container if called with no arguments', function() {
class TestService {}
const name = Math.random().toString(36).substring(7)
const defs = {}
defs[name] = TestService
const container = new Container(defs)
const services = container.service()
expect(util.types.isProxy(services)).to.be.true
expect(services[name]).to.be.an.instanceof(TestService)
})
})
describe('proxy function', function() {
@ -143,15 +155,15 @@ describe('the inversion of control container', function() {
})
})
describe('register function', function() {
describe('register_service function', function() {
it('should be a function', function() {
const container = new Container()
expect(container.register).to.be.a('function')
expect(container.register_service).to.be.a('function')
})
it('should accept 2 arguments', function() {
const container = new Container()
expect(container.register.length).to.be.equal(2)
expect(container.register_service.length).to.be.equal(2)
})
it('should register service definitions by name', function() {
@ -159,8 +171,8 @@ describe('the inversion of control container', function() {
const name = Math.random().toString(36).substring(7)
const container = new Container()
container.register(name, TestService)
expect(container.definitions[name]).to.be.equal(TestService)
container.register_service(name, TestService)
expect(container.definitions[name].ref).to.be.equal(TestService)
})
it('should trigger deferral processing', function() {
@ -168,8 +180,9 @@ describe('the inversion of control container', function() {
const name = Math.random().toString(36).substring(7)
const container = new Container()
container.deferred_classes = [{_di_deferred_services: [name]}]
container._process_deferral = sinon.spy()
container.register(name, TestService)
container.register_service(name, TestService)
expect(container._process_deferral.calledOnce).to.be.true
expect(container._process_deferral.getCall(0).args.length).to.be.equal(2)
expect(container._process_deferral.getCall(0).args[0]).to.be.equal(name)
@ -177,36 +190,25 @@ describe('the inversion of control container', function() {
})
})
describe('register_as_instance function', function() {
describe('register_singleton function', function() {
it('should be a function', function() {
const container = new Container()
expect(container.register_as_instance).to.be.a('function')
expect(container.register_singleton).to.be.a('function')
})
it('should accept 2 arguments', function() {
const container = new Container()
expect(container.register_as_instance.length).to.be.equal(2)
})
it('should register service constructors as definitions by name', function() {
class TestService {}
const name = Math.random().toString(36).substring(7)
const container = new Container()
const test = new TestService()
container.register_as_instance(name, test)
expect(container.definitions[name]).to.be.equal(TestService)
expect(container.register_singleton.length).to.be.equal(2)
})
it('should register service instances by name for reuse', function() {
it('should register singletons as definitions by name', function() {
class TestService {}
const name = Math.random().toString(36).substring(7)
const container = new Container()
const test = new TestService()
container.register_as_instance(name, test)
expect(container.service(name)).to.be.equal(test)
expect(container.service(name)).to.be.equal(test)
container.register_singleton(name, test)
expect(container.definitions[name].ref).to.be.equal(test)
})
it('should trigger deferral processing', function() {
@ -215,8 +217,9 @@ describe('the inversion of control container', function() {
const container = new Container()
const test = new TestService()
container.deferred_classes = [{_di_deferred_services: [name]}]
container._process_deferral = sinon.spy()
container.register_as_instance(name, test)
container.register_singleton(name, test)
expect(container._process_deferral.calledOnce).to.be.true
expect(container._process_deferral.getCall(0).args.length).to.be.equal(2)
expect(container._process_deferral.getCall(0).args[0]).to.be.equal(name)

151
test/DependencyInjector.spec.js

@ -24,7 +24,7 @@ describe('the dependency injector', function() {
expect(fake_container.di).to.be.equal(di)
})
describe('make function', function() {
describe('inject function', function() {
let container
let di
let fake_class
@ -36,36 +36,116 @@ describe('the dependency injector', function() {
})
it('should check if the class is injectable', function() {
di.make(fake_class)
di.inject(fake_class)
expect(di.__is_injectable.callCount).to.be.equal(1)
})
it('should fail if the class is not injectable', function() {
di.__is_injectable = sinon.fake.returns(false)
expect(di.make.bind(di, fake_class)).to.throw('Cannot inject non-injectable class: '+fake_class.name)
expect(di.inject.bind(di, fake_class)).to.throw('Cannot inject non-injectable class: '+fake_class.name)
})
it('should call the inject method on class', function() {
di.make(fake_class)
di.inject(fake_class)
expect(fake_class.__inject.calledOnce).to.be.true
})
it('should pass the container to the inject method', function() {
di.make(fake_class)
di.inject(fake_class)
expect(fake_class.__inject.getCall(0).args.length).to.be.equal(1)
expect(fake_class.__inject.getCall(0).args[0]).to.be.equal(container)
})
it('should return the class', function() {
expect(di.make(fake_class)).to.be.equal(fake_class)
expect(di.inject(fake_class)).to.be.equal(fake_class)
})
})
describe('make function', function() {
let container
let di
let fake_class
beforeEach(function() {
container = {}
di = new DependencyInjector(container)
di.__is_injectable = sinon.fake.returns(true)
di.inject = sinon.spy()
})
it('should be a function', function() {
expect(di.make).to.be.a('function')
})
it('should expect at least one argument', function() {
expect(di.make.length).to.be.equal(1)
})
it('should instantiate classes', function() {
class Test {}
expect(di.make(Test)).to.be.an.instanceof(Test)
})
it('should pass along constructor arguments', function() {
class Test {
constructor(foo, bar) {
this.foo = foo
this.bar = bar
}
}
const sym_1 = Symbol()
const sym_2 = Symbol()
const test = di.make(Test, sym_1, sym_2)
expect(test.foo).to.be.equal(sym_1)
expect(test.bar).to.be.equal(sym_2)
})
it('should inject injectable classes', function() {
class Test {}
di.make(Test)
expect(di.inject.calledOnce).to.be.true
expect(di.inject.getCall(0).args.length).to.be.equal(1)
expect(di.inject.getCall(0).args[0]).to.be.equal(Test)
})
it('should not inject normal classes', function() {
class Test {}
di.__is_injectable = sinon.fake.returns(false)
di.make(Test)
expect(di.inject.calledOnce).to.be.false
})
})
describe('service function', function() {
describe('get function', function() {
let container
let di
beforeEach(function() {
container = { service: sinon.spy() }
container = { get: sinon.spy() }
di = new DependencyInjector(container)
})
it('should be a function', function() {
expect(di.get).to.be.a('function')
})
it('should expect one argument', function() {
expect(di.get.length).to.be.equal(1)
})
it('should call the service method on the container', function() {
const arg = {}
di.get(arg)
expect(container.get.calledOnce).to.be.true
expect(container.get.getCall(0).args.length).to.be.equal(1)
expect(container.get.getCall(0).args[0]).to.be.equal(arg)
})
})
describe('[DEPRECATED] service function', function() {
let container
let di
beforeEach(function() {
container = { get: sinon.spy() }
di = new DependencyInjector(container)
})
@ -80,9 +160,9 @@ describe('the dependency injector', function() {
it('should call the service method on the container', function() {
const arg = {}
di.service(arg)
expect(container.service.calledOnce).to.be.true
expect(container.service.getCall(0).args.length).to.be.equal(1)
expect(container.service.getCall(0).args[0]).to.be.equal(arg)
expect(container.get.calledOnce).to.be.true
expect(container.get.getCall(0).args.length).to.be.equal(1)
expect(container.get.getCall(0).args[0]).to.be.equal(arg)
})
})
@ -138,6 +218,55 @@ describe('the dependency injector', function() {
expect(mod_proto.require).to.not.be.equal(original_require)
mod_proto.require = original_require
})
it('should call the inject function for injectable classes when they are loaded through require', function() {
const mod = require('module')
const original_require = mod.prototype.require
const sym_1 = Symbol()
mod.prototype.require = what => {
if ( what === 'module' ) return mod
else return sym_1
}
const di = new DependencyInjector()
const sym_2 = Symbol()
di.inject = sinon.fake.returns(sym_2)
di.__is_injectable = sinon.fake.returns(true)
di.inject_globally()
const test_val = require('this should not do anything')
expect(di.inject.calledOnce).to.be.true
expect(di.inject.getCall(0).args[0]).to.be.equal(sym_1)
expect(test_val).to.be.equal(sym_2)
mod.prototype.require = original_require
})
it('should not call the inject function if requires are not injectable', function() {
const mod = require('module')
const original_require = mod.prototype.require
const sym_1 = Symbol()
mod.prototype.require = what => {
if ( what === 'module' ) return mod
else return sym_1
}
const di = new DependencyInjector()
const sym_2 = Symbol()
di.inject = sinon.fake.returns(sym_2)
di.__is_injectable = sinon.fake.returns(false)
di.inject_globally()
const test_val = require('this should not do anything')
expect(di.inject.calledOnce).to.be.false
expect(test_val).to.be.equal(sym_1)
mod.prototype.require = original_require
})
})
describe('__is_injectable function', function() {

31
test/Injectable.spec.js

@ -64,15 +64,29 @@ describe('the injectable base class', function() {
class SomeService {}
FakeService = SomeService
fake_container = {
service: sinon.fake.returns(new FakeService)
get: sinon.fake.returns(new FakeService)
}
})
it('should request the services from the container, if deferral is disabled', function() {
InjClass._di_allow_defer = false
InjClass.__inject(fake_container)
expect(fake_container.service.calledOnce).to.be.true
expect(fake_container.service.getCall(0).args[0]).to.be.equal(svc_name)
expect(fake_container.get.calledOnce).to.be.true
expect(fake_container.get.getCall(0).args[0]).to.be.equal(svc_name)
})
it('should not defer the services if they do exist', function() {
InjClass._di_allow_defer = true
fake_container.has = sinon.fake.returns(true)
fake_container.defer = sinon.spy()
fake_container.get = sinon.spy()
InjClass.__inject(fake_container)
expect(fake_container.has.calledOnce).to.be.true
expect(InjClass._di_deferred_services).to.not.include(svc_name)
expect(InjClass._di_deferred_services.length).to.be.equal(0)
expect(fake_container.defer.calledOnce).to.be.false
expect(fake_container.get.calledOnce).to.be.true
expect(fake_container.get.getCall(0).args[0]).to.be.equal(svc_name)
})
it('should add the services to the class prototype from the container', function() {
@ -127,6 +141,17 @@ describe('the injectable base class', function() {
InjClass.__deferral_callback(name, fake_instance)
expect(InjClass._di_deferred_services).to.not.include(name)
})
it('should do nothing if there are no deferred services', function() {
const name = Math.random().toString(36).substring(7)
class InjClass extends Injectable {
static _di_deferred_services = []
static _di_deferred_instances = []
}
const fake_instance = {}
InjClass.__deferral_callback(name, fake_instance)
})
})
describe('constructor', function() {

4
test/integration/Integration.spec.js

@ -47,7 +47,7 @@ describe('[integration] basic service use', function() {
}
}
di.container.register(s2_name, Svc2)
di.container.register_service(s2_name, Svc2)
const s2 = di.container.service(s2_name)
expect(s2).to.be.an.instanceof(Svc2)
expect(s2[service_name]).to.be.an.instanceof(ServiceClass)
@ -73,7 +73,7 @@ describe('[integration] basic service use', function() {
expect(i2[service_name]).to.be.an.instanceof(ServiceClass)
expect(i2[s2_name]).to.be.undefined
di.container.register(s2_name, Svc2)
di.container.register_service(s2_name, Svc2)
expect(i2[s2_name]).to.be.an.instanceof(Svc2)
const i3 = new Inj2()

20
test/integration/Singleton.spec.js

@ -0,0 +1,20 @@
const { expect } = require('chai')
const Container = require('../../src/Container')
const Service = require('../../src/Service')
const DependencyInjector = require('../../src/DependencyInjector')
const Injectable = require('../../src/Injectable')
describe('[integration] IoC singleton registry', function() {
let container
let di
beforeEach(function() {
di = new DependencyInjector()
container = di.container
})
it('should allow registering singletons', function() {
const sym = Symbol('testing!')
expect(container.register_singleton('test singleton', sym)).to.be.undefined
expect(di.get('test singleton')).to.be.equal(sym)
})
})

801
yarn.lock

File diff suppressed because it is too large
Loading…
Cancel
Save