From 029e5c1ec39fd35c9edf74c680f7c742e12486f0 Mon Sep 17 00:00:00 2001 From: Carlo Zancanaro Date: Fri, 19 Sep 2014 12:19:31 +1000 Subject: Initial commit - minimal, synchronous, injector --- injector-tests.js | 200 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 200 insertions(+) create mode 100644 injector-tests.js (limited to 'injector-tests.js') diff --git a/injector-tests.js b/injector-tests.js new file mode 100644 index 0000000..339034f --- /dev/null +++ b/injector-tests.js @@ -0,0 +1,200 @@ +/*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); + }); + }); +}); -- cgit v1.2.3