/*global describe,it,expect,beforeEach*/ describe("injector", function() { var injector; beforeEach(function() { injector = new Injector(); }); describe("#register()", function() { it("should return the injector", function() { var result = injector.register("A", function(){ return "A"; }); expect(result).toBe(injector); }); it("should use a function's name if no name provided", function() { injector.register(function A(){ return "A"; }); expect(injector.specs.A).not.toBe(undefined); // IMPLEMENTATION DETAIL }); it("should register an object as a series of dependencies", function() { injector.register({ A: function(){ return "A"; }, B: function(){ return "B"; } }); expect(injector.specs.A).not.toBe(undefined); // IMPLEMENTATION DETAIL expect(injector.specs.B).not.toBe(undefined); // IMPLEMENTATION DETAIL }); }); describe("#get()", function() { it("should throw if the dependency can't be found", function() { expect(function() { injector.get("A"); }).toThrow(); }); }); describe("#invoke()", function() { it("should be able to invoke a function with no dependencies", function() { var A = injector.invoke(function() {return "functionResult";}); expect(A).toBe("functionResult"); }); it("should be able to invoke a function with a dependency in an array", function() { injector.register("B", function() {return "constructorB";}); var A = injector.invoke(["B", function(B) {return B + "A";}]); expect(A).toBe("constructorBA"); }); it("should be able to invoke a function with an implicit dependency", function() { injector.register("B", function() {return "constructorB";}); var A = injector.invoke(function(B) {return B + "A";}); expect(A).toBe("constructorBA"); }); it("should not touch any exceptions thrown by the function", function() { expect(function() { throw new Error("An error"); }).toThrow(new Error("An error")); }); it("should invoke the function with 'this' set to the injector", function() { var hasRun = false; injector.invoke(function() { hasRun = true; expect(this).toBe(injector); }); expect(hasRun).toBe(true); }); it("should not provide 'this.requestor' to the invoked function", function() { var caught = {}; try { injector.invoke(function() { this.requestor(); }); } catch (e) { caught = e; } expect(caught.name).toBe("InjectorError"); }); }); describe("constructor dependency injection", function() { it("should be able to register a constructor with no dependencies, and get the value for it", function() { injector.register("A", function() {return "constructorA";}); var A = injector.get("A"); expect(A).toBe("constructorA"); }); it("should be able to register a constructor with a dependency in an array, and get the value for it", function() { injector.register("B", function() {return "constructorB";}); injector.register("A", ["B", function(B) {return B;}]); var A = injector.get("A"); expect(A).toBe("constructorB"); }); it("should be able to register a constructor with an implicit dependency, and get the value for it", function() { injector.register("B", function() {return "constructorB";}); injector.register("A", function(B) {return B;}); var A = injector.get("A"); expect(A).toBe("constructorB"); }); it("should be able to register a constructor with an implicit name", function() { injector.register(function A() {return "constructorA";}); var A = injector.get("A"); expect(A).toBe("constructorA"); }); it("should be able to register constructors with an object", function() { injector.register({ A: ["B", function (B) {return "constructorA" + B;}], B: function(C) { return "B" + C; }, C: function() { return "C"; } }); var A = injector.get("A"); expect(A).toBe("constructorABC"); }); it("should wrap any exceptions thrown by constructors", function() { var error = new Error("constructorA"); injector.register("A", function() {throw error;}); var caught = {}; try { injector.get("A"); } catch (e) { caught = e; } expect(caught.name).toBe("InjectorError"); expect(caught.cause).toBe(error); }); it("should detect circular dependencies", function() { injector.register("A", ["B", function(B) {return null;}]); injector.register("B", ["A", function(A) {return null;}]); var caught = {message: ''}; try { injector.get("A"); } catch (e) { caught = e; } expect(caught.message.indexOf("Cyclic dependency detected")).toBe(0); }); it("should provide access to the requestor through 'this.requestor()'", function() { injector.register("B", function() { expect(this.requestor()).toBe("A"); return "returnValueB"; }); injector.register("A", ["B", function(B) { return B + "A"; }]); // make sure the function actually ran expect(injector.get("A")).toBe("returnValueBA"); }); }); describe("caching", function() { it("should return the same exact value for multiple requests of the same dependency", function() { injector.register("A", function() {return {an: 'object'};}); var first = injector.get("A"); var second = injector.get("A"); expect(first).toBe(second); }); it("shouldn't re-run constructors for cached values", function() { var numTimes = 0; injector.register("A", function() { numTimes++; if (numTimes > 1) throw new Error("Shouldn't be run more than once"); return {an: 'object'}; }); var first = injector.get("A"); var second = injector.get("A"); expect(first).toBe(second); }); it("shouldn't cache values which depend on their requestor", function() { var numTimes = 0; injector.register("A", function() { numTimes++; return "A" + this.requestor(); }); injector.register("B", ["A", function(A) {return "B" + A;}]); injector.register("C", ["A", function(A) {return "C" + A;}]); expect(injector.get("B")).toBe("BAB"); expect(injector.get("C")).toBe("CAC"); expect(numTimes).toBe(2); }); }); });