// Don't run code in ES5 strict mode. // In case this module was in strict mode, all other modules called by this would also be strict. // But when testing if the strict mode is preserved, we must ensure that this module is NOT strict. // These shared test cases are used to check if the provided implementation of rewire is compatible // with the original rewire. Since you can use rewire with client-side bundlers like webpack we need // to test the implementation there again. // @see https://github.com/jhnns/rewire-webpack var expect = require("expect.js"), rewire = require("rewire"), __set__Src = require("../lib/__set__.js").toString(), __get__Src = require("../lib/__get__.js").toString(), __with__Src = require("../lib/__with__.js").toString(); var supportsObjectSpread = (function () { try { eval("({...{}})"); return true; } catch (err) { return false; } })(); var supportsObjectRest = (function () { try { eval("const {...a} = {}"); return true; } catch (err) { return false; } })(); function checkForTypeError(err) { expect(err.constructor).to.be(TypeError); } module.exports = function () { it("should work like require()", function () { rewire("./moduleA.js").getFilename(); require("./moduleA.js").getFilename(); expect(rewire("./moduleA.js").getFilename()).to.eql(require("./moduleA.js").getFilename()); expect(rewire("../testLib/someOtherModule.js").filename).to.eql(require("../testLib/someOtherModule.js").filename); }); it("should return a fresh instance of the module", function () { var someOtherModule = require("./someOtherModule.js"), rewiredSomeOtherModule; someOtherModule.fs = "This has been modified"; rewiredSomeOtherModule = rewire("./someOtherModule.js"); expect(rewiredSomeOtherModule.fs).not.to.be("This has been modified"); }); it("should not cache the rewired module", function () { var rewired, someOtherModule = require("./someOtherModule.js"); someOtherModule.fs = "This has been changed"; rewired = rewire("./someOtherModule.js"); expect(someOtherModule).not.to.be(rewired); expect(require("./moduleA.js").someOtherModule).not.to.be(rewired); expect(require("./moduleA.js").someOtherModule).to.be(someOtherModule); expect(require("./moduleA.js").someOtherModule.fs).to.be("This has been changed"); }); // By comparing the src we can ensure that the provided __set__ function is our tested implementation it("should modify the module so it provides the __set__ - function", function () { expect(rewire("./moduleA.js").__set__.toString()).to.be(__set__Src); expect(rewire("./moduleB.js").__set__.toString()).to.be(__set__Src); }); // By comparing the src we can ensure that the provided __set__ function is our tested implementation it("should modify the module so it provides the __get__ - function", function () { expect(rewire("./moduleA.js").__get__.toString()).to.be(__get__Src); expect(rewire("./moduleB.js").__get__.toString()).to.be(__get__Src); }); // By comparing the src we can ensure that the provided __set__ function is our tested implementation it("should modify the module so it provides the __with__ - function", function () { expect(rewire("./moduleA.js").__with__.toString()).to.be(__with__Src); expect(rewire("./moduleB.js").__with__.toString()).to.be(__with__Src); }); ["__get__", "__set__", "__with__"].forEach(function(funcName) { it("should provide " + funcName + " as a non-enumerable property", function () { expect(Object.keys(rewire("./moduleA.js")).indexOf(funcName)).to.be(-1); }); it("should provide " + funcName + " as a writable property", function () { var obj = rewire("./moduleA.js"); var desc = Object.getOwnPropertyDescriptor(obj, funcName); expect(desc.writable).to.be(true); }); }); it("should not influence other modules", function () { rewire("./moduleA.js"); expect(require("./someOtherModule.js").__set__).to.be(undefined); expect(require("./someOtherModule.js").__get__).to.be(undefined); expect(require("./someOtherModule.js").__with__).to.be(undefined); }); it("should not override/influence global objects by default", function () { // This should throw no exception rewire("./moduleA.js").checkSomeGlobals(); rewire("./moduleB.js").checkSomeGlobals(); }); // This is just an integration test for the __set__ method // You can find a full test for __set__ under /test/__set__.test.js it("should provide a working __set__ method", function () { var rewiredModuleA = rewire("./moduleA.js"), newObj = {}; expect(rewiredModuleA.getMyNumber()).to.be(0); rewiredModuleA.__set__("myNumber", 2); expect(rewiredModuleA.getMyNumber()).to.be(2); rewiredModuleA.__set__("myObj", newObj); expect(rewiredModuleA.getMyObj()).to.be(newObj); rewiredModuleA.__set__("env", "ENVENV"); }); // This is just an integration test for the __get__ method // You can find a full test for __get__ under /test/__get__.test.js it("should provide a working __get__ method", function () { var rewiredModuleA = rewire("./moduleA.js"); expect(rewiredModuleA.__get__("myNumber")).to.be(rewiredModuleA.getMyNumber()); expect(rewiredModuleA.__get__("myObj")).to.be(rewiredModuleA.getMyObj()); }); // This is just an integration test for the __with__ method // You can find a full test for __with__ under /test/__with__.test.js it("should provide a working __with__ method", function () { var rewiredModuleA = rewire("./moduleA.js"), newObj = {}; expect(rewiredModuleA.getMyNumber()).to.be(0); expect(rewiredModuleA.getMyObj()).to.not.be(newObj); rewiredModuleA.__with__({ myNumber: 2, myObj: newObj })(function () { expect(rewiredModuleA.getMyNumber()).to.be(2); expect(rewiredModuleA.getMyObj()).to.be(newObj); }); expect(rewiredModuleA.getMyNumber()).to.be(0); expect(rewiredModuleA.getMyObj()).to.not.be(newObj); }); it("should provide the ability to inject mocks", function (done) { var rewiredModuleA = rewire("./moduleA.js"), mockedFs = { readFileSync: function (file) { expect(file).to.be("bla.txt"); done(); } }; rewiredModuleA.__set__("fs", mockedFs); rewiredModuleA.readFileSync(); }); it("should not influence other modules when injecting mocks", function () { var rewiredModuleA = rewire("./moduleA.js"), someOtherModule, mockedFs = {}; rewiredModuleA.__set__("fs", mockedFs); someOtherModule = require("./someOtherModule.js"); expect(someOtherModule.fs).not.to.be(mockedFs); }); it("should provide the ability to mock global objects just within the module", function () { var rewiredModuleA = rewire("./moduleA.js"), rewiredModuleB = rewire("./moduleB.js"), consoleMock = {}, bufferMock = {}, documentMock = {}, newFilename = "myFile.js"; rewiredModuleA.__set__({ console: consoleMock, __filename: newFilename }); expect(rewiredModuleA.getConsole()).to.be(consoleMock); expect(rewiredModuleB.getConsole()).not.to.be(consoleMock); expect(console).not.to.be(consoleMock); expect(rewiredModuleA.getFilename()).to.be(newFilename); expect(rewiredModuleB.getFilename()).not.to.be(newFilename); expect(console).not.to.be(newFilename); if (typeof window === "undefined") { rewiredModuleA.__set__("Buffer", bufferMock); expect(rewiredModuleA.getBuffer()).to.be(bufferMock); expect(rewiredModuleB.getBuffer()).not.to.be(bufferMock); expect(Buffer).not.to.be(bufferMock); } else { rewiredModuleA.__set__("document", documentMock); expect(rewiredModuleA.getDocument()).to.be(documentMock); expect(rewiredModuleB.getDocument() === documentMock).to.be(false); expect(document === documentMock).to.be(false); } }); it("should be possible to mock global objects that are added on runtime", function () { var rewiredModule; if (typeof window === "undefined") { global.someGlobalVar = "test"; rewiredModule = rewire("./moduleA.js"); rewiredModule.__set__("someGlobalVar", "other value"); expect(global.someGlobalVar).to.be("test"); expect(rewiredModule.__get__("someGlobalVar")).to.be("other value"); delete global.someGlobalVar; } else { window.someGlobalVar = "test"; rewiredModule = rewire("./moduleA.js"); rewiredModule.__set__("someGlobalVar", "other value"); expect(window.someGlobalVar).to.be("test"); expect(rewiredModule.__get__("someGlobalVar")).to.be("other value"); if (typeof navigator !== "undefined" && /MSIE [6-8]\.[0-9]/g.test(navigator.userAgent) === false) { delete window.someGlobalVar; } } }); it("should not be a problem to have a comment on file end", function () { var rewired = rewire("./emptyModule.js"); rewired.__set__("someVar", "hello"); expect(rewired.__get__("someVar")).to.be("hello"); }); it("should not be a problem to have a module that exports a boolean", function( ) { rewire("./boolean.js"); // should not throw }); it("should not be a problem to have a module that exports null", function () { rewire("./null.js"); // should not throw }); it("should not be a problem to have a module that exports a sealed object", function () { rewire("./sealedObject.js"); // should not throw }); (supportsObjectSpread ? it : it.skip)("should not be a problem to have a module that uses object spread operator", function () { rewire("./objectSpreadOperator.js"); // should not throw }); (supportsObjectRest ? it : it.skip)("should not be a problem to have a module that uses object rest operator", function () { rewire("./objectRestOperator.js"); // should not throw }); it("should not influence the original require if nothing has been required within the rewired module", function () { rewire("./emptyModule.js"); // nothing happens here because emptyModule doesn't require anything expect(require("./moduleA.js").__set__).to.be(undefined); // if restoring the original node require didn't worked, the module would have a setter }); it("subsequent calls of rewire should always return a new instance", function () { expect(rewire("./moduleA.js")).not.to.be(rewire("./moduleA.js")); }); it("should preserve the strict mode", function () { var strictModule = rewire("./strictModule.js"); expect(function () { strictModule.doSomethingUnstrict(); }).to.throwException(checkForTypeError); }); it("should not modify line numbers in stack traces", function () { var throwError = rewire("./throwError.js"); try { throwError(); } catch (err) { // Firefox implements a different error-stack format, // but does offer line and column numbers on errors: we use // those instead. if (err.lineNumber !== undefined && err.columnNumber !== undefined) { expect(err.lineNumber).to.equal(6) expect(err.columnNumber).to.equal(26) } // This is for the V8 stack trace format (Node, Chrome) else { expect(err.stack.split("\n")[1]).to.match(/:6:26/); } } }); it("should be possible to set implicit globals", function () { var implicitGlobalModule, err; try { implicitGlobalModule = rewire("./implicitGlobal.js"); implicitGlobalModule.__set__("implicitGlobal", true); expect(implicitGlobalModule.__get__("implicitGlobal")).to.be(true); // setting implicit global vars will change them globally instead of locally. // that's a shortcoming of the current implementation which can't be solved easily. //expect(implicitGlobal).to.be.a("string"); } catch (e) { err = e; } finally { // Cleaning up... delete global.implicitGlobal; delete global.undefinedImplicitGlobal; } if (err) { throw err; } }); it("should throw a TypeError if the path is not a string", function () { expect(function () { rewire(null); }).to.throwException(checkForTypeError); }); it("should also revert nested changes (with dot notation)", function () { var rewiredModuleA = rewire("./moduleA.js"), revert; revert = rewiredModuleA.__set__("myObj.test", true); expect(rewiredModuleA.getMyObj()).to.eql({ test: true }); revert(); // This test also demonstrates a known drawback of the current implementation // If the value doesn't exist at the time it is about to be set, it will be // reverted to undefined instead deleting it from the object // However, this is probably not a real world use-case because why would you // want to mock something when it is not set. expect(rewiredModuleA.getMyObj()).to.eql({ test: undefined }); revert = rewiredModuleA.__set__({ "myObj.test": true }); expect(rewiredModuleA.getMyObj()).to.eql({ test: true }); revert(); expect(rewiredModuleA.getMyObj()).to.eql({ test: undefined }); }); it("should be possible to mock undefined, implicit globals", function () { var implicitGlobalModule, err; try { implicitGlobalModule = rewire("./implicitGlobal.js"); implicitGlobalModule.__set__("undefinedImplicitGlobal", "yoo!"); expect(implicitGlobalModule.__get__("undefinedImplicitGlobal")).to.equal("yoo!"); implicitGlobalModule = rewire("./implicitGlobal.js"); implicitGlobalModule.__set__({ undefinedImplicitGlobal: "bro!" }); expect(implicitGlobalModule.__get__("undefinedImplicitGlobal")).to.equal("bro!"); } catch (e) { err = e; } finally { // Cleaning up... delete global.implicitGlobal; delete global.undefinedImplicitGlobal; } if (err) { throw err; } }); it("should be possible to mock and revert JSON.parse (see #40)", function () { var moduleA = rewire("./moduleA.js"), revert; revert = moduleA.__set__({ JSON: { parse: function () { return true; } } }); revert(); }); it("should be possible to set a const variable", function () { var constModule = rewire("./constModule"); var varNames = Object.keys(constModule); expect(varNames.length).to.be.greaterThan(0); varNames.forEach(varName => { constModule.__set__(varName, "this has been changed"); // should not throw expect(constModule[varName]()).to.equal("this has been changed"); }); }); it("should fail with a helpful TypeError when const is re-assigned", function () { expect(function () { rewire("./wrongConstModule"); }).to.throwException(/^Assignment to constant variable at .+?wrongConstModule\.js:4:1$/); }); };