Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/twbs/rewire.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--LICENSE18
-rw-r--r--Makefile4
-rw-r--r--lib/getLeakingSrc.js20
-rw-r--r--lib/getMonkeyPatchSrc.js33
-rw-r--r--lib/index.js13
-rw-r--r--lib/trick.js64
-rw-r--r--package.json43
-rw-r--r--test/getLeakingSrc.test.js16
-rw-r--r--test/getMonkeyPatchSrc.test.js30
-rw-r--r--test/testModules/A/moduleA.js21
-rw-r--r--test/testModules/B/moduleB.js7
-rw-r--r--test/testModules/C/moduleC.js3
-rw-r--r--test/testModules/index.js5
-rw-r--r--test/trick.test.js103
14 files changed, 380 insertions, 0 deletions
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..e19bc34
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,18 @@
+Copyright (c) 2012 Nathan MacInnes
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+the Software, and to permit persons to whom the Software is furnished to do so,
+subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..19c1a04
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,4 @@
+test:
+ mocha -R spec
+
+.PHONY: test
diff --git a/lib/getLeakingSrc.js b/lib/getLeakingSrc.js
new file mode 100644
index 0000000..c827411
--- /dev/null
+++ b/lib/getLeakingSrc.js
@@ -0,0 +1,20 @@
+"use strict"; // run code in ES5 strict mode
+
+function getLeakingSrc(leaks) {
+ var src = "exports.__ = {",
+ varName,
+ i;
+
+ for (i = 0; i < leaks.length; i++) {
+ varName = leaks[i];
+ src += (varName + ":" + varName + ",");
+ }
+ if (i > 0) {
+ src = src.slice(0, -1); // trim last comma
+ }
+ src += "};";
+
+ return src;
+}
+
+module.exports = getLeakingSrc;
diff --git a/lib/getMonkeyPatchSrc.js b/lib/getMonkeyPatchSrc.js
new file mode 100644
index 0000000..408b6ee
--- /dev/null
+++ b/lib/getMonkeyPatchSrc.js
@@ -0,0 +1,33 @@
+"use strict"; // run code in ES5 strict mode
+
+var toSrc = require("toSrc");
+
+function getMonkeyPatchSrc(obj) {
+ function walkObj(obj, level) {
+ var key,
+ value,
+ src = "";
+
+ for (key in obj) {
+ if (obj.hasOwnProperty(key)) {
+ value = obj[key];
+ if (typeof value === "object" && Array.isArray(value) === false) {
+ src += key + ".";
+ src += walkObj(value, level + 1);
+ } else {
+ if (level === 0) {
+ src += "var "; // in the top level, we need a var statement to override variables
+ }
+ src += key + "=" + toSrc(value, 9999) + ";";
+ }
+ }
+ }
+
+
+ return src;
+ }
+
+ return walkObj(obj, 0);
+}
+
+module.exports = getMonkeyPatchSrc; \ No newline at end of file
diff --git a/lib/index.js b/lib/index.js
new file mode 100644
index 0000000..f8f57ca
--- /dev/null
+++ b/lib/index.js
@@ -0,0 +1,13 @@
+"use strict"; // run code in ES5 strict mode
+
+var trick = require("./trick.js");
+
+module.exports = function (request, mocks, injections, leaks, cache) {
+ delete require.cache[__filename]; // deleting self from module cache so the parent module is always up to date
+
+ if (cache === undefined) {
+ cache = true;
+ }
+
+ return trick(module.parent, request, mocks, injections, leaks, cache);
+}; \ No newline at end of file
diff --git a/lib/trick.js b/lib/trick.js
new file mode 100644
index 0000000..36644be
--- /dev/null
+++ b/lib/trick.js
@@ -0,0 +1,64 @@
+"use strict"; // run code in ES5 strict mode
+
+var Module = require("module"),
+ nodeWrapper0 = Module.wrapper[0], // caching original wrapper
+ nodeWrapper1 = Module.wrapper[1],
+ getLeakingSrc = require("./getLeakingSrc.js"),
+ getMonkeyPatchSrc = require("./getMonkeyPatchSrc.js");
+
+function restoreOriginalWrappers() {
+ Module.wrapper[0] = nodeWrapper0;
+ Module.wrapper[1] = nodeWrapper1;
+}
+
+function trick(parentModule, filename, mocks, injections, leaks, cache) {
+ var testModule,
+ nodeRequire;
+
+ function requireTrick(path) {
+ restoreOriginalWrappers(); // we need to restore the wrappers now so we don't influence other modules
+
+ if (mocks && mocks.hasOwnProperty(path)) {
+ return mocks[path];
+ } else {
+ return nodeRequire.call(testModule, path); // node's require only works when "this" points to the module
+ }
+ }
+
+ // Checking params
+ if (typeof filename !== "string") {
+ throw new TypeError("Filename must be a string");
+ }
+
+ // Init vars
+ filename = Module._resolveFilename(filename, parentModule); // resolve full filename relative to the parent module
+ testModule = new Module(filename, parentModule);
+ nodeRequire = testModule.require; // caching original node require
+
+ // Prepare module for injection
+ if (typeof injections === "object") {
+ Module.wrapper[0] = nodeWrapper0 + getMonkeyPatchSrc(injections);
+ } else if (typeof injections === "string") {
+ Module.wrapper[0] = nodeWrapper0 + injections;
+ }
+
+ // Prepare module for leaking private vars
+ if (Array.isArray(leaks)) {
+ Module.wrapper[1] = getLeakingSrc(leaks) + nodeWrapper1;
+ }
+
+ // Mocking module.require-function
+ testModule.require = requireTrick;
+ // Loading module
+ testModule.load(testModule.id);
+
+ if (cache) {
+ require.cache[filename] = testModule;
+ }
+
+ restoreOriginalWrappers(); // this is only necessary if nothing has been required within the module
+
+ return testModule.exports;
+}
+
+module.exports = trick; \ No newline at end of file
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..34a1448
--- /dev/null
+++ b/package.json
@@ -0,0 +1,43 @@
+{
+ "name" : "trick",
+ "version" : "0.1.0",
+ "description" : "Dependency injection for node.js applications",
+ "keywords" : [
+ "dependency",
+ "injection",
+ "mock",
+ "unit",
+ "test"
+ ],
+ "author" : {
+ "name" : "Johannes Ewald",
+ "email" : "mail@johannesewald.de",
+ "web" : "http://johannesewald.de"
+ },
+ "main" : "lib/index.js",
+ "bugs" : {
+ "email" : "mail@johannesewald.de",
+ "url" : "https://github.com/jhnns/trick/issues"
+ },
+ "licenses" : {
+ "type" : "MIT",
+ "url" : "http: //www.opensource.org/licenses/mit-license.php"
+ },
+ "repositories" : {
+ "type" : "git",
+ "url" : "https://github.com/jhnns/trick"
+ },
+ "engines" : {
+ "node" : "0.6.x"
+ },
+ "dependencies": {
+ "toSrc": "0.1.x"
+ },
+ "devDependencies": {
+ "mocha": "1.1.x",
+ "expect.js": "0.1.x"
+ },
+ "scripts" : {
+ "test" : "make test"
+ }
+}
diff --git a/test/getLeakingSrc.test.js b/test/getLeakingSrc.test.js
new file mode 100644
index 0000000..cc215ea
--- /dev/null
+++ b/test/getLeakingSrc.test.js
@@ -0,0 +1,16 @@
+"use strict"; // run code in ES5 strict mode
+
+var expect = require("expect.js"),
+ getLeakingWrapper = require("../lib/getLeakingSrc.js");
+
+describe("#getLeakingWrapper", function () {
+ it("should return 'exports.__ = {};'", function () {
+ expect(getLeakingWrapper([])).to.be("exports.__ = {};");
+ });
+ it("should return 'exports.__ = {somethingPrivate:somethingPrivate,somethingSecret:somethingSecret};'", function () {
+ var leakArr = ["somethingPrivate", "somethingSecret"];
+
+ expect(getLeakingWrapper(leakArr))
+ .to.be("exports.__ = {somethingPrivate:somethingPrivate,somethingSecret:somethingSecret};");
+ });
+}); \ No newline at end of file
diff --git a/test/getMonkeyPatchSrc.test.js b/test/getMonkeyPatchSrc.test.js
new file mode 100644
index 0000000..d7cecaa
--- /dev/null
+++ b/test/getMonkeyPatchSrc.test.js
@@ -0,0 +1,30 @@
+"use strict"; // run code in ES5 strict mode
+
+var expect = require("expect.js"),
+ getMonkeyPatchSrc = require("../lib/getMonkeyPatchSrc.js");
+
+describe("#getMonkeyPatchSrc", function () {
+ it("should return ''", function () {
+ var expectedSrc = "",
+ subject = {};
+
+ expect(getMonkeyPatchSrc(subject)).to.be(expectedSrc);
+ });
+ it("should return 'process.argv=[\"myArg1\", \"myArg2\"];var console=456;'", function () {
+ var expectedSrc = "process.argv=[\"myArg1\", \"myArg2\"];var console=456;",
+ subject = {
+ process: {
+ argv: ["myArg1", "myArg2"]
+ },
+ console: 456
+ };
+
+ expect(getMonkeyPatchSrc(subject)).to.be(expectedSrc);
+ });
+ it("should return 'level1.level2.level3.level4.level5=true;", function () {
+ var expectedSrc = "level1.level2.level3.level4.level5=true;",
+ subject = {level1: {level2: {level3: {level4: {level5: true}}}}};
+
+ expect(getMonkeyPatchSrc(subject)).to.be(expectedSrc);
+ });
+}); \ No newline at end of file
diff --git a/test/testModules/A/moduleA.js b/test/testModules/A/moduleA.js
new file mode 100644
index 0000000..1599f01
--- /dev/null
+++ b/test/testModules/A/moduleA.js
@@ -0,0 +1,21 @@
+"use strict"; // run code in ES5 strict mode
+
+var path = require("path"),
+
+ // different ways to require a module
+ fs = require("fs"), // native module
+ c = require("../C/moduleC.js"), // relative path
+ b = require(path.resolve(__dirname, "../B/moduleB.js")), // absolute path
+ toSrc = require("toSrc"), // node_modules path
+ index = require("../"); // index.js path
+
+var myPrivateVar = "Hello I'm very private";
+
+// expose all required modules to test for mocks
+exports.fs = fs;
+exports.b = b;
+exports.c = c;
+exports.toSrc = toSrc;
+exports.index = index;
+exports.process = process;
+exports.console = console; \ No newline at end of file
diff --git a/test/testModules/B/moduleB.js b/test/testModules/B/moduleB.js
new file mode 100644
index 0000000..7b7b32c
--- /dev/null
+++ b/test/testModules/B/moduleB.js
@@ -0,0 +1,7 @@
+"use strict"; // run code in ES5 strict mode
+
+var c = require("../C/moduleC.js");
+
+exports.requireIndex = function () { // necessary to avoid circular dependency
+ exports.index = require("../index.js");
+}; \ No newline at end of file
diff --git a/test/testModules/C/moduleC.js b/test/testModules/C/moduleC.js
new file mode 100644
index 0000000..b7245de
--- /dev/null
+++ b/test/testModules/C/moduleC.js
@@ -0,0 +1,3 @@
+"use strict"; // run code in ES5 strict mode
+
+module.exports = "c"; \ No newline at end of file
diff --git a/test/testModules/index.js b/test/testModules/index.js
new file mode 100644
index 0000000..fa07160
--- /dev/null
+++ b/test/testModules/index.js
@@ -0,0 +1,5 @@
+"use strict"; // run code in ES5 strict mode
+
+exports.a = require("./A/moduleA.js");
+exports.b = require("./B/moduleB.js");
+exports.c = require("./C/moduleC.js"); \ No newline at end of file
diff --git a/test/trick.test.js b/test/trick.test.js
new file mode 100644
index 0000000..bb6633b
--- /dev/null
+++ b/test/trick.test.js
@@ -0,0 +1,103 @@
+"use strict"; // run code in ES5 strict mode
+
+var path = require("path"),
+ expect = require("expect.js"),
+ trick = require("../lib/index.js");
+
+var testModules = [
+ path.resolve(__dirname, "./testModules/index.js"),
+ path.resolve(__dirname, "./testModules/A/moduleA.js"),
+ path.resolve(__dirname, "./testModules/B/moduleB.js"),
+ path.resolve(__dirname, "./testModules/C/moduleC.js")
+ ];
+
+
+function cleanRequireCache() {
+ var i;
+
+ for (i = 0; i < testModules.length; i++) {
+ delete require.cache[testModules[i]];
+ }
+}
+
+describe("#trick", function () {
+ beforeEach(cleanRequireCache); // ensuring a clean test environment
+ it("should work like require() when omitting all other params", function () {
+ expect(trick("./testModules/A/moduleA.js")).to.be(require("./testModules/A/moduleA.js"));
+ });
+ it("should require all mocks", function () {
+ var tricked,
+ fsMock = {},
+ mocks = {},
+ moduleBMock = {},
+ moduleCMock = {},
+ toSrcMock = {},
+ indexMock = {};
+
+ mocks["fs"] = fsMock;
+ mocks[path.resolve(__dirname, "./testModules/B/moduleB.js")] = moduleBMock;
+ mocks["../C/moduleC.js"] = moduleCMock;
+ mocks["toSrc"] = toSrcMock;
+ mocks["../"] = indexMock;
+
+ tricked = trick("./testModules/A/moduleA.js", mocks);
+ expect(tricked.fs).to.be(fsMock);
+ expect(tricked.b).to.be(moduleBMock);
+ expect(tricked.c).to.be(moduleCMock);
+ expect(tricked.toSrc).to.be(toSrcMock);
+ expect(tricked.index).to.be(indexMock);
+ });
+ it("should inject object modifications", function () {
+ var tricked,
+ injections = {
+ process: {
+ argv: ["arg1", "arg2", "arg3"]
+ },
+ console: 123
+ };
+
+ tricked = trick("./testModules/A/moduleA.js", null, injections);
+ expect(tricked.process).to.be(process); // the process object itself should not be changed
+ expect(tricked.process.argv).to.be.eql(injections.process.argv);
+ expect(tricked.console).to.be(123);
+ });
+ it("should inject custom scripts", function () {
+ var tricked,
+ script = "var console = 456;";
+
+ tricked = trick("./testModules/A/moduleA.js", null, script);
+
+ expect(tricked.console).to.be(456);
+ });
+ it("should leak private variables", function () {
+ var tricked,
+ leaks = ["myPrivateVar"];
+
+ tricked = trick("./testModules/A/moduleA.js", null, null, leaks);
+ expect(tricked.__.myPrivateVar).to.be("Hello I'm very private");
+ });
+ it("should leak nothing on demand", function () {
+ var tricked;
+
+ tricked = trick("./testModules/A/moduleA.js");
+ expect(tricked.__).to.be(undefined);
+ });
+ it("should cache the tricked module", function () {
+ var tricked;
+
+ tricked = trick("./testModules/B/moduleB.js");
+ tricked.requireIndex();
+ expect(tricked.index.b).to.be(tricked);
+ cleanRequireCache();
+ tricked = trick("./testModules/B/moduleB.js", null, null, null, true);
+ tricked.requireIndex();
+ expect(tricked.index.b).to.be(tricked);
+ });
+ it("should not cache the tricked module on demand", function () {
+ var tricked;
+
+ tricked = trick("./testModules/B/moduleB.js", null, null, null, false);
+ tricked.requireIndex();
+ expect(tricked.index.b).not.to.be(tricked);
+ });
+}); \ No newline at end of file