miter
Version:
A typescript web framework based on ExpressJs based loosely on SailsJs
520 lines • 24.3 kB
JavaScript
;
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
Object.defineProperty(exports, "__esModule", { value: true });
const chai_1 = require("chai");
const sinon = require("sinon");
const sinonChai = require("sinon-chai");
chai_1.use(sinonChai);
const injector_1 = require("../injector");
const logger_core_1 = require("../../services/logger-core");
const injectable_decorator_1 = require("../../decorators/services/injectable.decorator");
const meta_decorator_1 = require("../../decorators/services/meta.decorator");
const name_decorator_1 = require("../../decorators/services/name.decorator");
describe('Injector', () => {
let loggerCore;
let instance;
beforeEach(() => {
loggerCore = new logger_core_1.LoggerCore('abc-xyz', 'default', false);
instance = new injector_1.Injector(loggerCore);
});
describe('.resolveInjectable', () => {
it('should provide the logger core when requested', () => {
chai_1.expect(instance.resolveInjectable(logger_core_1.LoggerCore)).to.equal(loggerCore);
});
it('should provide itself when requested', () => {
chai_1.expect(instance.resolveInjectable(injector_1.Injector)).to.equal(instance);
});
it('should instantiate a class that has not been created before', () => {
let ctorCalled = false;
let TestClass = class TestClass {
constructor() { ctorCalled = true; }
};
TestClass = __decorate([
injectable_decorator_1.Injectable(),
__metadata("design:paramtypes", [])
], TestClass);
chai_1.expect(instance.resolveInjectable(TestClass)).to.be.an.instanceOf(TestClass);
chai_1.expect(ctorCalled).to.be.true;
});
it('should not instantiate a class more than once', () => {
let ctorCallCount = 0;
let TestClass = class TestClass {
constructor() { ctorCallCount++; }
};
TestClass = __decorate([
injectable_decorator_1.Injectable(),
__metadata("design:paramtypes", [])
], TestClass);
let testInst = instance.resolveInjectable(TestClass);
chai_1.expect(testInst).to.be.an.instanceOf(TestClass);
chai_1.expect(ctorCallCount).to.eq(1);
chai_1.expect(instance.resolveInjectable(TestClass)).to.eq(testInst);
chai_1.expect(ctorCallCount).to.eq(1);
});
it('should throw an error if you try to inject a falsey type', () => {
chai_1.expect(() => instance.resolveInjectable(null)).to.throw('falsey');
});
it('should inject constructor parameters when it instantiates a type', () => {
let TestClassInner = class TestClassInner {
constructor() { }
};
TestClassInner = __decorate([
injectable_decorator_1.Injectable(),
__metadata("design:paramtypes", [])
], TestClassInner);
let TestClassOuter = class TestClassOuter {
constructor(inner) {
this.inner = inner;
}
};
TestClassOuter = __decorate([
injectable_decorator_1.Injectable(),
__metadata("design:paramtypes", [TestClassInner])
], TestClassOuter);
let outer = instance.resolveInjectable(TestClassOuter);
chai_1.expect(outer).not.to.be.undefined;
chai_1.expect(outer).to.be.an.instanceOf(TestClassOuter);
chai_1.expect(outer.inner).to.be.an.instanceOf(TestClassInner);
});
it('should invoke resolveDependencies when constructing a new instance', () => {
let TestClass2 = class TestClass2 {
constructor() { }
};
TestClass2 = __decorate([
injectable_decorator_1.Injectable(),
__metadata("design:paramtypes", [])
], TestClass2);
let TestClass = class TestClass {
constructor(tc2) { }
};
TestClass = __decorate([
injectable_decorator_1.Injectable(),
__metadata("design:paramtypes", [TestClass2])
], TestClass);
sinon.spy(instance, 'resolveDependencies');
instance.resolveInjectable(TestClass);
chai_1.expect(instance.resolveDependencies).to.have.been.calledTwice
.calledWith([TestClass2], TestClass)
.calledWith([], TestClass2);
});
it('should throw an error when the constructor parameters cannot be resolved', () => {
let TestClass = class TestClass {
constructor(dependency) {
this.dependency = dependency;
}
};
TestClass = __decorate([
injectable_decorator_1.Injectable(),
__metadata("design:paramtypes", [Object])
], TestClass);
chai_1.expect(() => instance.resolveInjectable(TestClass)).to.throw(/Failed to resolve dependencies/);
});
it('should throw an error when a constructor introduces a circular dependency', () => {
let TestClass = class TestClass {
constructor() { }
};
TestClass = __decorate([
injectable_decorator_1.Injectable(),
__metadata("design:paramtypes", [])
], TestClass);
let TestClass2 = class TestClass2 {
constructor() { }
};
TestClass2 = __decorate([
injectable_decorator_1.Injectable(),
__metadata("design:paramtypes", [])
], TestClass2);
Reflect.defineMetadata('design:paramtypes', [TestClass2], TestClass);
Reflect.defineMetadata('design:paramtypes', [TestClass], TestClass2);
chai_1.expect(() => instance.resolveInjectable(TestClass)).to.throw(/circular dependency/i);
});
});
describe('.provide', () => {
it('should throw when the provide value is falsey', () => {
chai_1.expect(() => instance.provide(null)).to.throw(/Invalid ProvideMetadata:/);
});
it('should throw when the provide constructor function is falsey', () => {
chai_1.expect(() => instance.provide({ provide: null, useValue: null })).to.throw(/provide a value for a falsey type/);
});
it('should throw when multiple values for a single type are provided', () => {
let TestClass = class TestClass {
constructor() { }
};
TestClass = __decorate([
injectable_decorator_1.Injectable(),
__metadata("design:paramtypes", [])
], TestClass);
instance.provide({ provide: TestClass, useValue: null });
chai_1.expect(() => instance.provide({ provide: TestClass, useValue: null })).to.throw(/Duplicate value provided/);
});
describe('when the provide metadata is a replacement class', () => {
it('should dependency inject a replacement class', () => {
let TestClass = class TestClass {
constructor() { }
};
TestClass = __decorate([
injectable_decorator_1.Injectable(),
__metadata("design:paramtypes", [])
], TestClass);
let FauxTestClass = class FauxTestClass {
constructor() { }
};
FauxTestClass = __decorate([
injectable_decorator_1.Injectable(),
__metadata("design:paramtypes", [])
], FauxTestClass);
;
instance.provide({ provide: TestClass, useClass: FauxTestClass });
chai_1.expect(instance.resolveInjectable(TestClass)).to.be.an.instanceOf(FauxTestClass);
});
});
describe('when the provide metadata is a replacement value', () => {
it('should dependency inject a replacement value', () => {
let TestClass = class TestClass {
constructor() { }
};
TestClass = __decorate([
injectable_decorator_1.Injectable(),
__metadata("design:paramtypes", [])
], TestClass);
let replacementValue = { myObj: 'someVal' };
instance.provide({ provide: TestClass, useValue: replacementValue });
chai_1.expect(instance.resolveInjectable(TestClass)).to.eq(replacementValue);
});
it('should dependency inject a falsey value', () => {
let TestClass = class TestClass {
constructor() { }
};
TestClass = __decorate([
injectable_decorator_1.Injectable(),
__metadata("design:paramtypes", [])
], TestClass);
instance.provide({ provide: TestClass, useValue: null });
chai_1.expect(instance.resolveInjectable(TestClass)).to.be.null;
});
});
describe('when the provide metadata is a factory function', () => {
it('should dependency inject a factory function', () => {
let TestClass = class TestClass {
constructor() { }
};
TestClass = __decorate([
injectable_decorator_1.Injectable(),
__metadata("design:paramtypes", [])
], TestClass);
let testInst = new TestClass();
instance.provide({ provide: TestClass, useCallback: () => testInst });
chai_1.expect(instance.resolveInjectable(TestClass)).to.eq(testInst);
});
it('should call the factory function every time the class is resolved', () => {
let TestClass = class TestClass {
constructor() { }
};
TestClass = __decorate([
injectable_decorator_1.Injectable(),
__metadata("design:paramtypes", [])
], TestClass);
let callbackCallCount = 0;
instance.provide({ provide: TestClass, useCallback: () => callbackCallCount++ });
instance.resolveInjectable(TestClass);
chai_1.expect(callbackCallCount).to.eq(1);
instance.resolveInjectable(TestClass);
chai_1.expect(callbackCallCount).to.eq(2);
});
it('should only call the factory function once if cache = true', () => {
let TestClass = class TestClass {
constructor() { }
};
TestClass = __decorate([
injectable_decorator_1.Injectable(),
__metadata("design:paramtypes", [])
], TestClass);
let callbackCallCount = 0;
instance.provide({ provide: TestClass, useCallback: () => callbackCallCount++, cache: true });
instance.resolveInjectable(TestClass);
chai_1.expect(callbackCallCount).to.eq(1);
instance.resolveInjectable(TestClass);
chai_1.expect(callbackCallCount).to.eq(1);
});
it('should dependency inject callback dependencies into the callback', () => {
let TestClass = class TestClass {
constructor() { }
};
TestClass = __decorate([
injectable_decorator_1.Injectable(),
__metadata("design:paramtypes", [])
], TestClass);
let TestClass2 = class TestClass2 {
constructor() { }
};
TestClass2 = __decorate([
injectable_decorator_1.Injectable(),
__metadata("design:paramtypes", [])
], TestClass2);
let testInst = new TestClass();
let callbackCallCount = 0;
instance.provide({ provide: TestClass, useCallback: (num2) => {
callbackCallCount++;
chai_1.expect(num2).to.be.an.instanceOf(TestClass2);
return testInst;
}, deps: [TestClass2] });
chai_1.expect(instance.resolveInjectable(TestClass)).to.eq(testInst);
});
it('should invoke resolveDependencies', () => {
let TestClass = class TestClass {
constructor() { }
};
TestClass = __decorate([
injectable_decorator_1.Injectable(),
__metadata("design:paramtypes", [])
], TestClass);
let meta = {
provide: TestClass,
useCallback: () => void (0),
deps: []
};
sinon.stub(instance, 'resolveDependencies').returns([]);
instance.provide(meta);
instance.resolveInjectable(TestClass);
chai_1.expect(instance.resolveDependencies).to.have.been.calledOnce.calledWith(meta.deps, TestClass);
});
it('should resolve string dependencies to defined metadata', () => {
let testAbc = 'my?test:abc!';
let TestClass2 = class TestClass2 {
constructor() { }
};
TestClass2 = __decorate([
injectable_decorator_1.Injectable(),
__metadata("design:paramtypes", [])
], TestClass2);
let TestClass = class TestClass {
constructor(tc2) {
this.tc2 = tc2;
}
};
TestClass = __decorate([
injectable_decorator_1.Injectable(),
meta_decorator_1.Meta('abc', testAbc),
__metadata("design:paramtypes", [TestClass2])
], TestClass);
let resolvedAbc = '';
let meta = {
provide: TestClass2,
useCallback: (abc) => resolvedAbc = abc,
deps: ['abc']
};
instance.provide(meta);
instance.resolveInjectable(TestClass);
chai_1.expect(resolvedAbc).to.eq(testAbc);
});
it('should not resolve string dependencies for metadata on the factory function class', () => {
let testAbc = 'my?test:abc!';
let TestClass = class TestClass {
constructor() { }
};
TestClass = __decorate([
injectable_decorator_1.Injectable(),
meta_decorator_1.Meta('abc', testAbc),
__metadata("design:paramtypes", [])
], TestClass);
let resolvedAbc = '';
let meta = {
provide: TestClass,
useCallback: (abc) => resolvedAbc = abc,
deps: ['abc']
};
instance.provide(meta);
instance.resolveInjectable(TestClass);
chai_1.expect(resolvedAbc).to.be.undefined;
});
it('should resolve string dependencies for metadata on the factory function class', () => {
let testAbc = 'my?test:abc!';
let TestClass = class TestClass {
constructor() { }
};
TestClass = __decorate([
injectable_decorator_1.Injectable(),
meta_decorator_1.Meta('name', testAbc),
__metadata("design:paramtypes", [])
], TestClass);
let resolvedAbc = '';
let meta = {
provide: TestClass,
useCallback: (abc) => resolvedAbc = abc,
deps: ['abc']
};
instance.provide(meta);
instance.resolveInjectable(TestClass);
chai_1.expect(resolvedAbc).to.be.undefined;
});
it(`should resolve the 'name' dependency even if the name has been explicitly defined`, () => {
let TestClass2 = class TestClass2 {
constructor() { }
};
TestClass2 = __decorate([
injectable_decorator_1.Injectable(),
__metadata("design:paramtypes", [])
], TestClass2);
let TestClass = class TestClass {
constructor(tc2) {
this.tc2 = tc2;
}
};
TestClass = __decorate([
injectable_decorator_1.Injectable(),
__metadata("design:paramtypes", [TestClass2])
], TestClass);
let resolvedName = '';
let meta = {
provide: TestClass2,
useCallback: (name) => resolvedName = name,
deps: ['name']
};
instance.provide(meta);
instance.resolveInjectable(TestClass);
chai_1.expect(resolvedName).to.eq(TestClass.name);
});
it(`should allow a custom 'name' metadata to be explicitly defined`, () => {
let testName = 'my!!!!test:name##';
let TestClass2 = class TestClass2 {
constructor() { }
};
TestClass2 = __decorate([
injectable_decorator_1.Injectable(),
__metadata("design:paramtypes", [])
], TestClass2);
let TestClass = class TestClass {
constructor(tc2) {
this.tc2 = tc2;
}
};
TestClass = __decorate([
injectable_decorator_1.Injectable(),
name_decorator_1.Name(testName),
__metadata("design:paramtypes", [TestClass2])
], TestClass);
let resolvedName = '';
let meta = {
provide: TestClass2,
useCallback: (name) => resolvedName = name,
deps: ['name']
};
instance.provide(meta);
instance.resolveInjectable(TestClass);
chai_1.expect(resolvedName).to.eq(testName);
});
it('should resolve string dependencies for metadata on the factory function class when resolving other dependencies', () => {
let testName = 'my?test:name!';
let resolvedName = '';
let TestClass3 = class TestClass3 {
constructor() { }
};
TestClass3 = __decorate([
injectable_decorator_1.Injectable(),
__metadata("design:paramtypes", [])
], TestClass3);
let TestClass2 = class TestClass2 {
constructor(tc3) {
this.tc3 = tc3;
}
};
TestClass2 = __decorate([
injectable_decorator_1.Injectable(),
meta_decorator_1.Meta('name', testName + '2'),
__metadata("design:paramtypes", [TestClass3])
], TestClass2);
let TestClass = class TestClass {
constructor(tc2) {
this.tc2 = tc2;
}
};
TestClass = __decorate([
injectable_decorator_1.Injectable(),
meta_decorator_1.Meta('name', testName),
__metadata("design:paramtypes", [TestClass2])
], TestClass);
let meta3 = {
provide: TestClass3,
useCallback: (name) => resolvedName = name,
deps: ['name']
};
instance.provide(meta3);
let meta2 = {
provide: TestClass2,
useCallback: (tc3) => void (0),
deps: [TestClass3]
};
instance.provide(meta2);
instance.resolveInjectable(TestClass);
chai_1.expect(resolvedName).to.eq(testName + '2');
});
it('should throw an error when the factory function depends on itself', () => {
let TestClass = class TestClass {
constructor() { }
};
TestClass = __decorate([
injectable_decorator_1.Injectable(),
__metadata("design:paramtypes", [])
], TestClass);
let meta = {
provide: TestClass,
useCallback: () => void (0),
deps: [TestClass]
};
instance.provide(meta);
chai_1.expect(() => instance.resolveInjectable(TestClass)).to.throw(/circular dependency/i);
});
it('should throw an error when a factory function introduces a circular dependency', () => {
let TestClass = class TestClass {
constructor() { }
};
TestClass = __decorate([
injectable_decorator_1.Injectable(),
__metadata("design:paramtypes", [])
], TestClass);
let TestClass2 = class TestClass2 {
constructor(tc) {
this.tc = tc;
}
};
TestClass2 = __decorate([
injectable_decorator_1.Injectable(),
__metadata("design:paramtypes", [TestClass])
], TestClass2);
let meta = {
provide: TestClass,
useCallback: (tc2) => void (0),
deps: [TestClass2]
};
instance.provide(meta);
chai_1.expect(() => instance.resolveInjectable(TestClass)).to.throw(/circular dependency/i);
});
});
});
describe('.resolveDependencies', () => {
let resolveDependencies;
beforeEach(() => {
resolveDependencies = instance.resolveDependencies.bind(instance);
});
it('should throw when the dependencies cannot be resolved', () => {
let TestClass = class TestClass {
constructor() { }
};
TestClass = __decorate([
injectable_decorator_1.Injectable(),
__metadata("design:paramtypes", [])
], TestClass);
chai_1.expect(() => resolveDependencies([null], TestClass)).to.throw(/Failed to resolve dependencies/);
chai_1.expect(() => resolveDependencies([Object], TestClass)).to.throw(/Failed to resolve dependencies/);
});
});
});
//# sourceMappingURL=injector.spec.js.map