Browse Source

More unit tests and code coverage

tags/ci-01
garrettmills 6 months ago
parent
commit
a5fb09e99e
No known key found for this signature in database GPG Key ID: 6ACD58D6ADACFC6E
8 changed files with 1039 additions and 68 deletions
  1. +1
    -0
      .gitignore
  2. +3
    -1
      package.json
  3. +49
    -46
      test/Container.spec.js
  4. +140
    -11
      test/DependencyInjector.spec.js
  5. +28
    -3
      test/Injectable.spec.js
  6. +2
    -2
      test/integration/Integration.spec.js
  7. +20
    -0
      test/integration/Singleton.spec.js
  8. +796
    -5
      yarn.lock

+ 1
- 0
.gitignore View File

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

+ 3
- 1
package.json View File

@@ -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"
}
}

+ 49
- 46
test/Container.spec.js View File

@@ -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)


+ 140
- 11
test/DependencyInjector.spec.js View File

@@ -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() {


+ 28
- 3
test/Injectable.spec.js View File

@@ -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() {


+ 2
- 2
test/integration/Integration.spec.js View File

@@ -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
- 0
test/integration/Singleton.spec.js View File

@@ -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)
})
})

+ 796
- 5
yarn.lock
File diff suppressed because it is too large
View File


Loading…
Cancel
Save