├── .editorconfig ├── .github └── workflows │ └── test.yml ├── .gitignore ├── .jshintignore ├── .jshintrc ├── .npmignore ├── .nvmrc ├── .nycrc ├── CHANGELOG.md ├── LICENSE ├── README.md ├── lib ├── __get__.js ├── __set__.js ├── __with__.js ├── detectStrictMode.js ├── getDefinePropertySrc.js ├── getImportGlobalsSrc.js ├── index.js ├── moduleEnv.js └── rewire.js ├── package-lock.json ├── package.json ├── tea.yaml ├── test ├── __get__.test.js ├── __set__.test.js ├── __with__.test.js ├── detectStrictMode.test.js ├── getImportGlobalsSrc.test.js └── rewire.test.js └── testLib ├── .babelrc ├── boolean.js ├── constModule.js ├── debuggerModule.js ├── emptyModule.js ├── implicitGlobal.js ├── module.ts ├── moduleA.js ├── moduleB.js ├── node_modules └── rewire │ └── package.json ├── null.js ├── objectRestOperator.js ├── objectSpreadOperator.js ├── sealedObject.js ├── sharedTestCases.js ├── shebangModule.js ├── someOtherModule.js ├── strictModule.js ├── throwError.js └── wrongConstModule.js /.editorconfig: -------------------------------------------------------------------------------- 1 | # This file is for unifying the coding style for different editors and IDEs. 2 | # More information at http://EditorConfig.org 3 | 4 | # No .editorconfig files above the root directory 5 | root = true 6 | 7 | [*] 8 | indent_style = space 9 | indent_size = 4 10 | charset = utf-8 11 | trim_trailing_whitespace = true 12 | insert_final_newline = true 13 | 14 | [package.json] 15 | indent_size = 2 16 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: 🧪 Test 2 | 3 | on: 4 | push: 5 | branches: 6 | - "master" 7 | pull_request: {} 8 | 9 | jobs: 10 | test: 11 | runs-on: ubuntu-latest 12 | if: "!contains(github.event.head_commit.message, '[skip ci]')" 13 | 14 | strategy: 15 | matrix: 16 | node-version: [18.x, 20.x] 17 | 18 | steps: 19 | - name: 🛑 Cancel Previous Runs 20 | uses: styfle/cancel-workflow-action@a40b8845c0683271d9f53dfcb887a7e181d3918b # pin@0.9.1 21 | - name: ⬇️ Checkout repo 22 | uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f # pin@v2 23 | - name: ⎔ Setup node ${{ matrix.node-version }} 24 | uses: actions/setup-node@25316bbc1f10ac9d8798711f44914b1cf3c4e954 # pin@v2 25 | with: 26 | node-version: ${{ matrix.node-version }} 27 | cache: "npm" 28 | - name: 🗄 Cache node_modules 29 | id: cache-node_modules 30 | uses: actions/cache@c64c572235d810460d0d6876e9c705ad5002b353 # pin@v2 31 | with: 32 | path: "**/node_modules" 33 | key: node_modules-${{ runner.os }}-node-${{ matrix.node-version }}-${{ 34 | hashFiles('**/package-lock.json') }} 35 | - name: 📥 Install dependencies 36 | if: steps.cache-node_modules.outputs.cache-hit != 'true' 37 | run: | 38 | npm ci --ignore-scripts 39 | - name: 🧪 Test 40 | run: | 41 | npm test 42 | env: 43 | CI: true 44 | - name: ⬆️ Upload coverage report 45 | uses: coverallsapp/github-action@9ba913c152ae4be1327bfb9085dc806cedb44057 # pin@1.1.3 46 | with: 47 | github-token: ${{ secrets.GITHUB_TOKEN }} 48 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | lib-cov 2 | *.seed 3 | *.log 4 | *.csv 5 | *.dat 6 | *.out 7 | *.pid 8 | *.gz 9 | 10 | pids 11 | logs 12 | results 13 | 14 | npm-debug.log 15 | /node_modules 16 | /coverage 17 | /examples 18 | /.idea 19 | 20 | .nyc_output 21 | -------------------------------------------------------------------------------- /.jshintignore: -------------------------------------------------------------------------------- 1 | lib-cov 2 | *.seed 3 | *.log 4 | *.csv 5 | *.dat 6 | *.out 7 | *.pid 8 | *.gz 9 | 10 | pids 11 | logs 12 | results 13 | 14 | npm-debug.log 15 | /node_modules 16 | /coverage 17 | /examples 18 | /.idea 19 | 20 | /lib/__get__.js 21 | /lib/__set__.js 22 | /test/testModules/strictModule.js 23 | /test/testModules/someOtherModule.js -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "browser": true, 3 | "node": true, 4 | "expr": true, 5 | "globals": ["describe", "it", "before", "after", "beforeEach", "afterEach"] 6 | } -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | lib-cov 2 | *.seed 3 | *.log 4 | *.csv 5 | *.dat 6 | *.out 7 | *.pid 8 | *.gz 9 | 10 | pids 11 | logs 12 | results 13 | 14 | npm-debug.log 15 | /node_modules 16 | /coverage 17 | /examples 18 | /.idea 19 | /test 20 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | v20.3.1 2 | -------------------------------------------------------------------------------- /.nycrc: -------------------------------------------------------------------------------- 1 | { 2 | "exclude": [ 3 | "lib/__get__.js", 4 | "lib/__set__.js", 5 | "lib/__with__.js", 6 | "testLib/*" 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | Changelog 2 | --------- 3 | 4 | ### 7.0.0 5 | - **Breaking**: Remove official Node v10, v12, v14 and v16 support. We had to do this because one of our dependencies had security issues and the version with the fix dropped Node v10 as well. Additionally, there were also package-lock.json issues because of a breaking change at npm [6deb9bd3edb1d3531ffa689968339f9fd390a5d5](https://github.com/jhnns/rewire/commit/6deb9bd3edb1d3531ffa689968339f9fd390a5d5) [092e554955db2591d09b57d3b87a575ee0d510a9](https://github.com/jhnns/rewire/commit/092e554955db2591d09b57d3b87a575ee0d510a9) 6 | - **Breaking**: Remove CoffeeScript support [e0ea17d2e13ef4fb054980c1c5c62edcfd10632f](https://github.com/jhnns/rewire/commit/e0ea17d2e13ef4fb054980c1c5c62edcfd10632f) 7 | - Add TypeScript support [#204](https://github.com/jhnns/rewire/pull/204) 8 | 9 | ### 6.0.0 10 | - **Breaking**: Remove Node v8 support. We had to do this because one of our dependencies had security issues and the version with the fix dropped Node v8 as well. 11 | - Update dependencies [#193](https://github.com/jhnns/rewire/issues/193) 12 | - Fix Modifying globals within module leaks to global with Node >=10 [#167](https://github.com/jhnns/rewire/issues/167) 13 | - Fixed import errors on modules with shebang declarations [#179](https://github.com/jhnns/rewire/pull/179) 14 | 15 | ### 5.0.0 16 | - **Breaking**: Remove Node v6 support. We had to do this because one of our dependencies had security issues and the version with the fix dropped Node v6 as well. 17 | - Update dependencies [#159](https://github.com/jhnns/rewire/pull/159) [#172](https://github.com/jhnns/rewire/issues/172) [#154](https://github.com/jhnns/rewire/issues/154) [#166](https://github.com/jhnns/rewire/issues/166) 18 | 19 | ### 4.0.1 20 | - Fix a bug where `const` was not properly detected [#139](https://github.com/jhnns/rewire/pull/139) 21 | 22 | ### 4.0.0 23 | - **Breaking**: Remove official node v4 support. It probably still works with node v4, but no guarantees anymore. 24 | - **Potentially breaking**: Replace babel with regex-based transformation [9b77ed9a293c538ec3eb5160bcb933e012ce517f](https://github.com/jhnns/rewire/commit/9b77ed9a293c538ec3eb5160bcb933e012ce517f). 25 | This should not break, but it has been flagged as major version bump as the regex might not catch all cases reliably and thus fail for some users. 26 | - Improve runtime performance [#132](https://github.com/jhnns/rewire/issues/132) 27 | - Use `coffeescript` package in favor of deprecated `coffee-script` [#134](https://github.com/jhnns/rewire/pull/134) 28 | 29 | ### 3.0.2 30 | - Fix a bug where rewire used the project's .babelrc [#119](https://github.com/jhnns/rewire/issues/119) [#123](https://github.com/jhnns/rewire/pull/123) 31 | 32 | ### 3.0.1 33 | - Fix Unknown Plugin "transform-es2015-block-scoping" [#121](https://github.com/jhnns/rewire/issues/121) [#122](https://github.com/jhnns/rewire/pull/122) 34 | 35 | ### 3.0.0 36 | - **Breaking:** Remove support for node versions below 4 37 | - Add support for `const` [#79](https://github.com/jhnns/rewire/issues/79) [#95](https://github.com/jhnns/rewire/issues/95) [#117](https://github.com/jhnns/rewire/pull/117) [#118](https://github.com/jhnns/rewire/pull/118) 38 | 39 | ### 2.5.2 40 | - Fix cluttering of `require.extensions` even if CoffeeScript is not installed [#98](https://github.com/jhnns/rewire/pull/98) 41 | 42 | ### 2.5.1 43 | - Ignore modules that export non-extensible values like primitives or sealed objects [#83](https://github.com/jhnns/rewire/pull/83) 44 | 45 | ### 2.5.0 46 | - Provide shared test cases to other modules that mimic rewire's behavior in other environments [jhnns/rewire-webpack#18](https://github.com/jhnns/rewire-webpack/pull/18) 47 | 48 | ### 2.4.0 49 | - Make rewire's special methods `__set__`, `__get__` and `__with__` writable [#78](https://github.com/jhnns/rewire/pull/78) 50 | 51 | ### 2.3.4 52 | - Add license and keywords to package.json [#59](https://github.com/jhnns/rewire/issues/59) [#60](https://github.com/jhnns/rewire/issues/60) 53 | 54 | ### 2.3.3 55 | - Fix issue where the strict mode was not detected when a comment was before "strict mode"; [#54](https://github.com/jhnns/rewire/issues/54) 56 | 57 | ### 2.3.2 58 | - Fix a problem when a function declaration had the same name as a global variable [#56](https://github.com/jhnns/rewire/issues/56) 59 | - Add README section about rewire's limitations 60 | 61 | ### 2.3.1 62 | - Fix problems when global objects like JSON, etc. have been rewired [#40](https://github.com/jhnns/rewire/issues/40) 63 | 64 | ### 2.3.0 65 | - Add possibility to mock undefined, implicit globals [#35](https://github.com/jhnns/rewire/issues/35) 66 | 67 | ### 2.2.0 68 | - Add support for dot notation in __set__(env) calls [#39](https://github.com/jhnns/rewire/issues/39) 69 | 70 | ### 2.1.5 71 | - Fix issues with reverting nested properties [#39](https://github.com/jhnns/rewire/issues/39) 72 | 73 | ### 2.1.4 74 | - Fix problems when an illegal variable name is used for a global 75 | 76 | ### 2.1.3 77 | - Fix shadowing of internal `module`, `exports` and `require` when a global counterpart exists [jhnns/rewire-webpack#6](https://github.com/jhnns/rewire-webpack/pull/6) 78 | 79 | ### 2.1.2 80 | - Fixed missing `var` statement which lead to pollution of global namespace [#33](https://github.com/jhnns/rewire/pull/33) 81 | 82 | ### 2.1.1 83 | - Made magic `__set__`, `__get__` and `__with__` not enumerable [#32](https://github.com/jhnns/rewire/pull/32) 84 | 85 | ### 2.1.0 86 | - Added revert feature of `__set__` method 87 | - Introduced `__with__` method to revert changes automatically 88 | 89 | ### 2.0.1 90 | - Added test coverage tool 91 | - Small README and description changes 92 | 93 | ### 2.0.0 94 | - Removed client-side bundler extensions. Browserify is not supported anymore. Webpack support has been extracted 95 | into separate repository https://github.com/jhnns/rewire-webpack 96 | 97 | ### 1.1.3 98 | - Removed IDE stuff from npm package 99 | 100 | ### 1.1.2 101 | - Added deprecation warning for client-side bundlers 102 | - Updated package.json for node v0.10 103 | 104 | ### 1.1.1 105 | - Fixed bug with modules that had a comment on the last line 106 | 107 | ### 1.1.0 108 | - Added Coffee-Script support 109 | - Removed Makefile: Use `npm test` instead. 110 | 111 | ### 1.0.4 112 | - Improved client-side rewire() with webpack 113 | 114 | ### 1.0.3 115 | - Fixed error with client-side bundlers when a module was ending with a comment 116 | 117 | ### 1.0.2 118 | - Improved strict mode detection 119 | 120 | ### 1.0.1 121 | - Fixed crash when a global module has been used in the browser 122 | 123 | ### 1.0.0 124 | - Removed caching functionality. Now rewire doesn't modify `require.cache` at all 125 | - Added support for [webpack](https://github.com/webpack/webpack)-bundler 126 | - Moved browserify-middleware from `rewire.browserify` to `rewire.bundlers.browserify` 127 | - Reached stable state :) 128 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012 Johannes Ewald 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | this software and associated documentation files (the "Software"), to deal in 5 | the Software without restriction, including without limitation the rights to 6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 7 | the Software, and to permit persons to whom the Software is furnished to do so, 8 | subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 15 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 16 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 17 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 18 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | rewire 2 | ====== 3 | **Easy monkey-patching for node.js unit tests** 4 | 5 | [![](https://img.shields.io/npm/v/rewire.svg)](https://www.npmjs.com/package/rewire) 6 | [![](https://img.shields.io/npm/dm/rewire.svg)](https://www.npmjs.com/package/rewire) 7 | [![Coverage Status](https://img.shields.io/coveralls/jhnns/rewire.svg)](https://coveralls.io/r/jhnns/rewire?branch=master) 8 | 9 | rewire adds a special setter and getter to modules so you can modify their behaviour for better unit testing. You may 10 | 11 | - inject mocks for other modules or globals like `process` 12 | - inspect private variables 13 | - override variables within the module. 14 | 15 | **Please note:** The current version of rewire is only compatible with CommonJS modules. See [Limitations](https://github.com/jhnns/rewire#limitations). 16 | 17 |
18 | 19 | Installation 20 | ------------ 21 | 22 | `npm install rewire` 23 | 24 |
25 | 26 | Introduction 27 | ------------ 28 | 29 | Imagine you want to test this module: 30 | 31 | ```javascript 32 | // lib/myModule.js 33 | // With rewire you can change all these variables 34 | var fs = require("fs"), 35 | path = "/somewhere/on/the/disk"; 36 | 37 | function readSomethingFromFileSystem(cb) { 38 | console.log("Reading from file system ..."); 39 | fs.readFile(path, "utf8", cb); 40 | } 41 | 42 | exports.readSomethingFromFileSystem = readSomethingFromFileSystem; 43 | ``` 44 | 45 | Now within your test module: 46 | 47 | ```javascript 48 | // test/myModule.test.js 49 | var rewire = require("rewire"); 50 | 51 | var myModule = rewire("../lib/myModule.js"); 52 | ``` 53 | 54 | rewire acts exactly like require. With just one difference: Your module will now export a special setter and getter for private variables. 55 | 56 | ```javascript 57 | myModule.__set__("path", "/dev/null"); 58 | myModule.__get__("path"); // = '/dev/null' 59 | ``` 60 | 61 | This allows you to mock everything in the top-level scope of the module, like the fs module for example. Just pass the variable name as first parameter and your mock as second. 62 | 63 | ```javascript 64 | var fsMock = { 65 | readFile: function (path, encoding, cb) { 66 | expect(path).to.equal("/somewhere/on/the/disk"); 67 | cb(null, "Success!"); 68 | } 69 | }; 70 | myModule.__set__("fs", fsMock); 71 | 72 | myModule.readSomethingFromFileSystem(function (err, data) { 73 | console.log(data); // = Success! 74 | }); 75 | ``` 76 | 77 | You can also set multiple variables with one call. 78 | 79 | ```javascript 80 | myModule.__set__({ 81 | fs: fsMock, 82 | path: "/dev/null" 83 | }); 84 | ``` 85 | 86 | You may also override globals. These changes are only within the module, so you don't have to be concerned that other modules are influenced by your mock. 87 | 88 | ```javascript 89 | myModule.__set__({ 90 | console: { 91 | log: function () { /* be quiet */ } 92 | }, 93 | process: { 94 | argv: ["testArg1", "testArg2"] 95 | } 96 | }); 97 | ``` 98 | 99 | `__set__` returns a function which reverts the changes introduced by this particular `__set__` call 100 | 101 | ```javascript 102 | var revert = myModule.__set__("port", 3000); 103 | 104 | // port is now 3000 105 | revert(); 106 | // port is now the previous value 107 | ``` 108 | 109 | For your convenience you can also use the `__with__` method which reverts the given changes after it finished. 110 | 111 | ```javascript 112 | myModule.__with__({ 113 | port: 3000 114 | })(function () { 115 | // within this function port is 3000 116 | }); 117 | // now port is the previous value again 118 | ``` 119 | 120 | The `__with__` method is also aware of promises. If a thenable is returned all changes stay until the promise has either been resolved or rejected. 121 | 122 | ```javascript 123 | myModule.__with__({ 124 | port: 3000 125 | })(function () { 126 | return new Promise(...); 127 | }).then(function () { 128 | // now port is the previous value again 129 | }); 130 | // port is still 3000 here because the promise hasn't been resolved yet 131 | ``` 132 | 133 |
134 | 135 | Limitations 136 | ----------- 137 | 138 | **Babel's ES module emulation**
139 | During the transpilation step from ESM to CJS modules, Babel renames internal variables. Rewire will not work in these cases (see [#62](https://github.com/jhnns/rewire/issues/62)). Other Babel transforms, however, should be fine. Another solution might be switching to [babel-plugin-rewire](https://github.com/speedskater/babel-plugin-rewire). 140 | 141 | **Variables inside functions**
142 | Variables inside functions can not be changed by rewire. This is constrained by the language. 143 | 144 | ```javascript 145 | // myModule.js 146 | (function () { 147 | // Can't be changed by rewire 148 | var someVariable; 149 | })() 150 | ``` 151 | 152 | **Modules that export primitives**
153 | rewire is not able to attach the `__set__`- and `__get__`-method if your module is just exporting a primitive. Rewiring does not work in this case. 154 | 155 | ```javascript 156 | // Will throw an error if it's loaded with rewire() 157 | module.exports = 2; 158 | ``` 159 | 160 | **Globals with invalid variable names**
161 | rewire imports global variables into the local scope by prepending a list of `var` declarations: 162 | 163 | ```javascript 164 | var someGlobalVar = global.someGlobalVar; 165 | ``` 166 | 167 | If `someGlobalVar` is not a valid variable name, rewire just ignores it. **In this case you're not able to override the global variable locally**. 168 | 169 | **Special globals**
170 | Please be aware that you can't rewire `eval()` or the global object itself. 171 | 172 | 173 |
174 | 175 | API 176 | --- 177 | 178 | ### rewire(filename: String): rewiredModule 179 | 180 | Returns a rewired version of the module found at `filename`. Use `rewire()` exactly like `require()`. 181 | 182 | ### rewiredModule.__set__(name: String, value: *): Function 183 | 184 | Sets the internal variable `name` to the given `value`. Returns a function which can be called to revert the change. 185 | 186 | ### rewiredModule.__set__(obj: Object): Function 187 | 188 | Takes all enumerable keys of `obj` as variable names and sets the values respectively. Returns a function which can be called to revert the change. 189 | 190 | ### rewiredModule.__get__(name: String): * 191 | 192 | Returns the private variable with the given `name`. 193 | 194 | ### rewiredModule.__with__(obj: Object): Function<callback: Function> 195 | 196 | Returns a function which - when being called - sets `obj`, executes the given `callback` and reverts `obj`. If `callback` returns a promise, `obj` is only reverted after the promise has been resolved or rejected. For your convenience the returned function passes the received promise through. 197 | 198 |
199 | 200 | Caveats 201 | ------- 202 | 203 | **Difference to require()**
204 | Every call of rewire() executes the module again and returns a fresh instance. 205 | 206 | ```javascript 207 | rewire("./myModule.js") === rewire("./myModule.js"); // = false 208 | ``` 209 | 210 | This can especially be a problem if the module is not idempotent [like mongoose models](https://github.com/jhnns/rewire/issues/27). 211 | 212 | **Globals are imported into the module's scope at the time of rewiring**
213 | Since rewire imports all gobals into the module's scope at the time of rewiring, property changes on the `global` object after that are not recognized anymore. This is a [problem when using sinon's fake timers *after* you've called `rewire()`](http://stackoverflow.com/questions/34885024/when-using-rewire-and-sinon-faketimer-order-matters/36025128). 214 | 215 | **Dot notation**
216 | Although it is possible to use dot notation when calling `__set__`, it is strongly discouraged in most cases. For instance, writing `myModule.__set__("console.log", fn)` is effectively the same as just writing `console.log = fn`. It would be better to write: 217 | 218 | ```javascript 219 | myModule.__set__("console", { 220 | log: function () {} 221 | }); 222 | ``` 223 | 224 | This replaces `console` just inside `myModule`. That is, because rewire is using `eval()` to turn the key expression into an assignment. Hence, calling `myModule.__set__("console.log", fn)` modifies the `log` function on the *global* `console` object. 225 | 226 |
227 | 228 | webpack 229 | ------- 230 | See [rewire-webpack](https://github.com/jhnns/rewire-webpack) 231 | 232 |
233 | 234 | ## License 235 | 236 | MIT 237 | -------------------------------------------------------------------------------- /lib/__get__.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This function will be stringified and then injected into every rewired module. 3 | * Then you can leak private variables by calling myModule.__get__("myPrivateVar"); 4 | * 5 | * All variables within this function are namespaced in the arguments array because every 6 | * var declaration could possibly clash with a variable in the module scope. 7 | * 8 | * @param {!String} name name of the variable to retrieve 9 | * @throws {TypeError} 10 | * @return {*} 11 | */ 12 | function __get__() { 13 | arguments.varName = arguments[0]; 14 | if (arguments.varName && typeof arguments.varName === "string") { 15 | return eval(arguments.varName); 16 | } else { 17 | throw new TypeError("__get__ expects a non-empty string"); 18 | } 19 | } 20 | 21 | module.exports = __get__; -------------------------------------------------------------------------------- /lib/__set__.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This function will be stringified and then injected into every rewired module. 3 | * Then you can set private variables by calling myModule.__set__("myPrivateVar", newValue); 4 | * 5 | * All variables within this function are namespaced in the arguments array because every 6 | * var declaration could possibly clash with a variable in the module scope. 7 | * 8 | * @param {String|Object} varName name of the variable to set 9 | * @param {String} varValue new value 10 | * @return {Function} 11 | */ 12 | function __set__() { 13 | arguments.varName = arguments[0]; 14 | arguments.varValue = arguments[1]; 15 | // Saving references to global objects and functions. Thus a test may even change these variables 16 | // without interfering with rewire(). 17 | // @see https://github.com/jhnns/rewire/issues/40 18 | arguments.refs = arguments[2] || { 19 | isArray: Array.isArray, 20 | TypeError: TypeError, 21 | stringify: JSON.stringify 22 | // We can't save eval() because eval() is a *special* global function 23 | // That's why it can't be re-assigned in strict mode 24 | //eval: eval 25 | }; 26 | arguments.src = ""; 27 | arguments.revertArgs = []; 28 | 29 | if (typeof arguments[0] === "object") { 30 | arguments.env = arguments.varName; 31 | if (!arguments.env || arguments.refs.isArray(arguments.env)) { 32 | throw new arguments.refs.TypeError("__set__ expects an object as env"); 33 | } 34 | arguments.revertArgs[0] = {}; 35 | for (arguments.varName in arguments.env) { 36 | if (arguments.env.hasOwnProperty(arguments.varName)) { 37 | arguments.varValue = arguments.env[arguments.varName]; 38 | arguments.src += arguments.varName + " = arguments.env[" + arguments.refs.stringify(arguments.varName) + "]; "; 39 | try { 40 | // Allow tests to mock implicit globals 41 | // @see https://github.com/jhnns/rewire/issues/35 42 | arguments.revertArgs[0][arguments.varName] = eval(arguments.varName); 43 | } catch (err) { 44 | arguments.revertArgs[0][arguments.varName] = undefined; 45 | } 46 | } 47 | } 48 | } else if (typeof arguments.varName === "string") { 49 | if (!arguments.varName) { 50 | throw new arguments.refs.TypeError("__set__ expects a non-empty string as a variable name"); 51 | } 52 | arguments.src = arguments.varName + " = arguments.varValue;"; 53 | try { 54 | // Allow tests to mock implicit globals 55 | // @see https://github.com/jhnns/rewire/issues/35 56 | arguments.revertArgs = [arguments.varName, eval(arguments.varName)]; 57 | } catch (err) { 58 | arguments.revertArgs = [arguments.varName, undefined]; 59 | } 60 | } else { 61 | throw new arguments.refs.TypeError("__set__ expects an environment object or a non-empty string as a variable name"); 62 | } 63 | 64 | // Passing our saved references on to the revert function 65 | arguments.revertArgs[2] = arguments.refs; 66 | 67 | eval(arguments.src); 68 | 69 | return function (revertArgs) { 70 | __set__.apply(null, revertArgs); 71 | }.bind(null, arguments.revertArgs); 72 | } 73 | 74 | module.exports = __set__; 75 | -------------------------------------------------------------------------------- /lib/__with__.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | /** 4 | * This function will be stringified and then injected into every rewired module. 5 | * 6 | * Calling myModule.__with__("myPrivateVar", newValue) returns a function where 7 | * you can place your tests. As long as the returned function is executed variables 8 | * will be set to the given value, after that all changed variables are reset back to normal. 9 | * 10 | * @param {String|Object} varName name of the variable to set 11 | * @param {String} varValue new value 12 | * @return {Function} 13 | */ 14 | function __with__() { 15 | var args = arguments; 16 | 17 | return function (callback) { 18 | var undo, 19 | returned, 20 | isPromise; 21 | 22 | if (typeof callback !== "function") { 23 | throw new TypeError("__with__ expects a callback function"); 24 | } 25 | 26 | undo = module.exports.__set__.apply(null, args); 27 | 28 | try { 29 | returned = callback(); 30 | isPromise = returned && typeof returned.then === "function"; 31 | if (isPromise) { 32 | returned.then(undo, undo); 33 | return returned; 34 | } 35 | } finally { 36 | if (!isPromise) { 37 | undo(); 38 | } 39 | } 40 | }; 41 | } 42 | 43 | module.exports = __with__; -------------------------------------------------------------------------------- /lib/detectStrictMode.js: -------------------------------------------------------------------------------- 1 | var multiLineComment = /^\s*\/\*.*?\*\//; 2 | var singleLineComment = /^\s*\/\/.*?[\r\n]/; 3 | var strictMode = /^\s*(?:"use strict"|'use strict')[ \t]*(?:[\r\n]|;)/; 4 | 5 | /** 6 | * Returns true if the source code is intended to run in strict mode. Does not detect 7 | * "use strict" if it occurs in a nested function. 8 | * 9 | * @param {String} src 10 | * @return {Boolean} 11 | */ 12 | function detectStrictMode(src) { 13 | var singleLine; 14 | var multiLine; 15 | 16 | while ((singleLine = singleLineComment.test(src)) || (multiLine = multiLineComment.test(src))) { 17 | if (singleLine) { 18 | src = src.replace(singleLineComment, ""); 19 | } 20 | if (multiLine) { 21 | src = src.replace(multiLineComment, ""); 22 | } 23 | } 24 | 25 | return strictMode.test(src); 26 | } 27 | 28 | module.exports = detectStrictMode; 29 | -------------------------------------------------------------------------------- /lib/getDefinePropertySrc.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var __get__ = require("./__get__.js"); 4 | var __set__ = require ("./__set__.js"); 5 | var __with__ = require("./__with__.js"); 6 | 7 | var srcs = { 8 | "__get__": __get__.toString(), 9 | "__set__": __set__.toString(), 10 | "__with__": __with__.toString() 11 | }; 12 | 13 | function getDefinePropertySrc() { 14 | var src = "if (typeof(module.exports) === 'function' || \n" + 15 | "(typeof(module.exports) === 'object' && module.exports !== null && Object.isExtensible(module.exports))) {\n"; 16 | 17 | src += Object.keys(srcs).reduce(function forEachSrc(preValue, value) { 18 | return preValue += "Object.defineProperty(module.exports, '" + 19 | value + 20 | "', {enumerable: false, value: " + 21 | srcs[value] + 22 | ", "+ 23 | "writable: true}); "; 24 | }, ""); 25 | 26 | src += "\n}"; 27 | 28 | return src; 29 | } 30 | 31 | module.exports = getDefinePropertySrc; 32 | -------------------------------------------------------------------------------- /lib/getImportGlobalsSrc.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Declares all globals with a var and assigns the global object. Thus you're able to 3 | * override globals without changing the global object itself. 4 | * 5 | * Returns something like 6 | * "var console = global.console; var process = global.process; ..." 7 | * 8 | * @return {String} 9 | */ 10 | function getImportGlobalsSrc(ignore) { 11 | var key, 12 | src = "", 13 | globalObj = typeof global === "undefined"? window: global; 14 | 15 | ignore = ignore || []; 16 | ignore.push( 17 | // global itself can't be overridden because it's the only reference to our real global objects 18 | "global", 19 | // ignore 'module', 'exports' and 'require' on the global scope, because otherwise our code would 20 | // shadow the module-internal variables 21 | // @see https://github.com/jhnns/rewire-webpack/pull/6 22 | "module", "exports", "require", 23 | // strict mode doesn't allow to (re)define 'undefined', 'eval' & 'arguments' 24 | "undefined", "eval", "arguments", 25 | // 'GLOBAL' and 'root' are deprecated in Node 26 | // (assigning them causes a DeprecationWarning) 27 | "GLOBAL", "root", 28 | // 'NaN' and 'Infinity' are immutable 29 | // (doesn't throw an error if you set 'var NaN = ...', but doesn't work either) 30 | "NaN", "Infinity", 31 | ); 32 | 33 | const globals = Object.getOwnPropertyNames(globalObj); 34 | 35 | for (key of globals) { 36 | if (ignore.indexOf(key) !== -1) { 37 | continue; 38 | } 39 | 40 | // key may be an invalid variable name (e.g. 'a-b') 41 | try { 42 | eval("var " + key + ";"); 43 | src += "var " + key + " = global." + key + "; "; 44 | } catch(e) {} 45 | } 46 | 47 | return src; 48 | } 49 | 50 | module.exports = getImportGlobalsSrc; 51 | -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | var rewireModule = require("./rewire.js"); 2 | 3 | /** 4 | * Adds a special setter and getter to the module located at filename. After the module has been rewired, you can 5 | * call myModule.__set__(name, value) and myModule.__get__(name) to manipulate private variables. 6 | * 7 | * @param {!String} filename Path to the module that shall be rewired. Use it exactly like require(). 8 | * @return {*} the rewired module 9 | */ 10 | function rewire(filename) { 11 | return rewireModule(module.parent, filename); 12 | } 13 | 14 | module.exports = rewire; 15 | 16 | delete require.cache[__filename]; // deleting self from module cache so the parent module is always up to date 17 | -------------------------------------------------------------------------------- /lib/moduleEnv.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | // TODO: Use https://www.npmjs.com/package/pirates here? 4 | 5 | var Module = require("module"), 6 | eslint = require("eslint"); 7 | 8 | var moduleWrapper0 = Module.wrapper[0], 9 | moduleWrapper1 = Module.wrapper[1], 10 | originalExtensions = {}, 11 | linter = new eslint.Linter(), 12 | eslintOptions = { 13 | env: { 14 | es6: true, 15 | }, 16 | parserOptions: { 17 | ecmaVersion: 6, 18 | ecmaFeatures: { 19 | globalReturn: true, 20 | jsx: true, 21 | experimentalObjectRestSpread: true 22 | }, 23 | }, 24 | rules: { 25 | "no-const-assign": 2 26 | } 27 | }, 28 | // The following regular expression is used to replace const declarations with let. 29 | // This regex replacement is not 100% safe because transforming JavaScript requires an actual parser. 30 | // However, parsing (e.g. via babel) comes with its own problems because now the parser needs to 31 | // be aware of syntax extensions which might not be supported by the parser, but the underlying 32 | // JavaScript engine. In fact, rewire used to have babel in place here but required an extra 33 | // transform for the object spread operator (check out commit d9a81c0cdacf6995b24d205b4a2068adbd8b34ff 34 | // or see https://github.com/jhnns/rewire/pull/128). It was also notable slower 35 | // (see https://github.com/jhnns/rewire/issues/132). 36 | // There is another issue: replacing const with let is not safe because of their different behavior. 37 | // That's why we also have ESLint in place which tries to identify this error case. 38 | // There is one edge case though: when a new syntax is used *and* a const re-assignment happens, 39 | // rewire would compile happily in this situation but the actual code wouldn't work. 40 | // However, since most projects have a seperate linting step which catches these const re-assignment 41 | // errors anyway, it's probably still a reasonable trade-off. 42 | // Test the regular expresssion at https://regex101.com/r/dvnZPv/2 and also check out testLib/constModule.js. 43 | matchConst = /(^|\s|\}|;)const(\/\*|\s|{)/gm, 44 | // Required for importing modules with shebang declarations, since NodeJS 12.16.0 45 | shebang = /^#!.+/, 46 | nodeRequire, 47 | currentModule; 48 | 49 | function load(targetModule) { 50 | nodeRequire = targetModule.require; 51 | targetModule.require = requireProxy; 52 | currentModule = targetModule; 53 | 54 | registerExtensions(); 55 | targetModule.load(targetModule.id); 56 | 57 | // This is only necessary if nothing has been required within the module 58 | reset(); 59 | } 60 | 61 | function reset() { 62 | Module.wrapper[0] = moduleWrapper0; 63 | Module.wrapper[1] = moduleWrapper1; 64 | } 65 | 66 | function inject(prelude, appendix) { 67 | Module.wrapper[0] = moduleWrapper0 + prelude; 68 | Module.wrapper[1] = appendix + moduleWrapper1; 69 | } 70 | 71 | /** 72 | * Proxies the first require call in order to draw back all changes to the Module.wrapper. 73 | * Thus our changes don't influence other modules 74 | * 75 | * @param {!String} path 76 | */ 77 | function requireProxy(path) { 78 | reset(); 79 | currentModule.require = nodeRequire; 80 | return nodeRequire.call(currentModule, path); // node's require only works when "this" points to the module 81 | } 82 | 83 | function registerExtensions() { 84 | var originalJsExtension = require.extensions[".js"]; 85 | var originalTsExtension = require.extensions[".ts"]; 86 | 87 | if (originalJsExtension) { 88 | originalExtensions.js = originalJsExtension; 89 | } 90 | if (originalTsExtension) { 91 | originalExtensions.ts = originalTsExtension; 92 | } 93 | require.extensions[".js"] = jsExtension; 94 | require.extensions[".ts"] = tsExtension; 95 | } 96 | 97 | function restoreExtensions() { 98 | if ("js" in originalExtensions) { 99 | require.extensions[".js"] = originalExtensions.js; 100 | } 101 | if ("ts" in originalExtensions) { 102 | require.extensions[".ts"] = originalExtensions.ts; 103 | } 104 | } 105 | 106 | function isNoConstAssignMessage(message) { 107 | return message.ruleId === "no-const-assign"; 108 | } 109 | 110 | function jsExtension(module, filename) { 111 | var _compile = module._compile; 112 | 113 | module._compile = function (content, filename) { 114 | var noConstAssignMessage = linter.verify(content, eslintOptions).find(isNoConstAssignMessage); 115 | var line; 116 | var column; 117 | 118 | if (noConstAssignMessage !== undefined) { 119 | line = noConstAssignMessage.line; 120 | column = noConstAssignMessage.column; 121 | throw new TypeError(`Assignment to constant variable at ${ filename }:${ line }:${ column }`); 122 | } 123 | 124 | _compile.call( 125 | module, 126 | content 127 | .replace(shebang, '') // Remove shebang declarations 128 | .replace(matchConst, "$1let $2"), // replace const with let, while maintaining the column width 129 | filename 130 | ); 131 | }; 132 | 133 | restoreExtensions(); 134 | originalExtensions.js(module, filename); 135 | } 136 | 137 | function tsExtension(module, filename) { 138 | var _compile = module._compile; 139 | 140 | module._compile = function rewireCompile(content, filename) { 141 | var noConstAssignMessage = linter.verify(content, eslintOptions).find(isNoConstAssignMessage); 142 | var line; 143 | var column; 144 | 145 | if (noConstAssignMessage !== undefined) { 146 | line = noConstAssignMessage.line; 147 | column = noConstAssignMessage.column; 148 | throw new TypeError(`Assignment to constant variable at ${ filename }:${ line }:${ column }`); 149 | } 150 | _compile.call( 151 | this, 152 | content 153 | .replace(shebang, '') // Remove shebang declarations 154 | .replace(matchConst, "$1let $2"), // replace const with let, while maintaining the column width 155 | filename 156 | ); 157 | }; 158 | 159 | restoreExtensions(); 160 | originalExtensions.ts(module, filename); 161 | } 162 | 163 | exports.load = load; 164 | exports.inject = inject; 165 | -------------------------------------------------------------------------------- /lib/rewire.js: -------------------------------------------------------------------------------- 1 | var Module = require("module"), 2 | fs = require("fs"), 3 | getImportGlobalsSrc = require("./getImportGlobalsSrc.js"), 4 | getDefinePropertySrc = require("./getDefinePropertySrc.js"), 5 | detectStrictMode = require("./detectStrictMode.js"), 6 | moduleEnv = require("./moduleEnv.js"); 7 | 8 | /** 9 | * Does actual rewiring the module. For further documentation @see index.js 10 | */ 11 | function internalRewire(parentModulePath, targetPath) { 12 | var targetModule, 13 | prelude, 14 | appendix, 15 | src; 16 | 17 | // Checking params 18 | if (typeof targetPath !== "string") { 19 | throw new TypeError("Filename must be a string"); 20 | } 21 | 22 | // Resolve full filename relative to the parent module 23 | targetPath = Module._resolveFilename(targetPath, parentModulePath); 24 | 25 | // Create testModule as it would be created by require() 26 | targetModule = new Module(targetPath, parentModulePath); 27 | 28 | // We prepend a list of all globals declared with var so they can be overridden (without changing original globals) 29 | prelude = getImportGlobalsSrc(); 30 | 31 | // Wrap module src inside IIFE so that function declarations do not clash with global variables 32 | // @see https://github.com/jhnns/rewire/issues/56 33 | prelude += "(function () { "; 34 | 35 | // We append our special setter and getter. 36 | appendix = "\n" + getDefinePropertySrc(); 37 | 38 | // End of IIFE 39 | appendix += "})();"; 40 | 41 | // Check if the module uses the strict mode. 42 | // If so we must ensure that "use strict"; stays at the beginning of the module. 43 | src = fs.readFileSync(targetPath, "utf8"); 44 | if (detectStrictMode(src) === true) { 45 | prelude = ' "use strict"; ' + prelude; 46 | } 47 | 48 | moduleEnv.inject(prelude, appendix); 49 | moduleEnv.load(targetModule); 50 | 51 | return targetModule.exports; 52 | } 53 | 54 | module.exports = internalRewire; 55 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "rewire", 3 | "version": "7.0.0", 4 | "description": "Easy dependency injection for node.js unit testing", 5 | "keywords": [ 6 | "dependency", 7 | "injection", 8 | "mock", 9 | "shim", 10 | "module", 11 | "unit", 12 | "test", 13 | "leak", 14 | "inspect", 15 | "fake", 16 | "require" 17 | ], 18 | "author": { 19 | "name": "Johannes Ewald", 20 | "email": "mail@johannesewald.de" 21 | }, 22 | "main": "lib/index.js", 23 | "homepage": "https://github.com/jhnns/rewire", 24 | "bugs": { 25 | "url": "https://github.com/jhnns/rewire/issues", 26 | "email": "mail@johannesewald.de" 27 | }, 28 | "repository": { 29 | "type": "git", 30 | "url": "git://github.com/jhnns/rewire.git" 31 | }, 32 | "devDependencies": { 33 | "@types/node": "^17.0.35", 34 | "expect.js": "^0.3.1", 35 | "mocha": "^10.0.0", 36 | "nyc": "^15.1.0", 37 | "rewire": "file://.", 38 | "ts-node": "^10.8.0", 39 | "typescript": "^4.6.4" 40 | }, 41 | "license": "MIT", 42 | "scripts": { 43 | "test": "nyc --reporter=html --reporter=lcov mocha -r ts-node/register -R spec" 44 | }, 45 | "dependencies": { 46 | "eslint": "^8.47.0" 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /tea.yaml: -------------------------------------------------------------------------------- 1 | # https://tea.xyz/what-is-this-file 2 | --- 3 | version: 1.0.0 4 | codeOwners: 5 | - '0x5663bc6C57F8E07bDF6A31831F6612cCbA5ECD3D' 6 | quorum: 1 7 | -------------------------------------------------------------------------------- /test/__get__.test.js: -------------------------------------------------------------------------------- 1 | var expect = require("expect.js"), 2 | vm = require("vm"), 3 | __get__ = require("../lib/__get__.js"), 4 | 5 | expectReferenceError = expectError(ReferenceError), 6 | expectTypeError = expectError(TypeError); 7 | 8 | function expectError(ErrConstructor) { 9 | return function expectReferenceError(err) { 10 | expect(err.constructor.name).to.be(ErrConstructor.name); 11 | }; 12 | } 13 | 14 | 15 | describe("__get__", function () { 16 | var moduleFake; 17 | 18 | beforeEach(function () { 19 | moduleFake = { 20 | __filename: "some/file.js", 21 | myNumber: 0, 22 | myObj: {} 23 | }; 24 | 25 | vm.runInNewContext( 26 | "__get__ = " + __get__.toString() + "; " + 27 | "setNumber = function (value) { myNumber = value; }; " + 28 | "setObj = function (value) { myObj = value; }; ", 29 | moduleFake, 30 | __filename 31 | ); 32 | }); 33 | it("should return the initial value", function () { 34 | expect(moduleFake.__get__("myNumber")).to.be(0); 35 | expect(moduleFake.__get__("myObj")).to.eql({}); 36 | }); 37 | it("should return the changed value of the number", function () { 38 | var newObj = { hello: "hello" }; 39 | 40 | moduleFake.setNumber(2); 41 | moduleFake.setObj(newObj); 42 | expect(moduleFake.__get__("myNumber")).to.be(2); 43 | expect(moduleFake.__get__("myObj")).to.be(newObj); 44 | }); 45 | it("should throw a ReferenceError when getting not existing vars", function () { 46 | expect(function () { 47 | moduleFake.__get__("blabla"); 48 | }).to.throwException(expectReferenceError); 49 | }); 50 | it("should throw a TypeError when passing misfitting params", function () { 51 | expect(function () { 52 | moduleFake.__get__(); 53 | }).to.throwException(expectTypeError); 54 | expect(function () { 55 | moduleFake.__get__(undefined); 56 | }).to.throwException(expectTypeError); 57 | expect(function () { 58 | moduleFake.__get__(null); 59 | }).to.throwException(expectTypeError); 60 | expect(function () { 61 | moduleFake.__get__(true); 62 | }).to.throwException(expectTypeError); 63 | expect(function () { 64 | moduleFake.__get__(2); 65 | }).to.throwException(expectTypeError); 66 | expect(function () { 67 | moduleFake.__get__(""); 68 | }).to.throwException(expectTypeError); 69 | expect(function () { 70 | moduleFake.__get__([]); 71 | }).to.throwException(expectTypeError); 72 | expect(function () { 73 | moduleFake.__get__({}); 74 | }).to.throwException(expectTypeError); 75 | expect(function () { 76 | moduleFake.__get__(function () {}); 77 | }).to.throwException(expectTypeError); 78 | }); 79 | }); -------------------------------------------------------------------------------- /test/__set__.test.js: -------------------------------------------------------------------------------- 1 | var expect = require("expect.js"), 2 | __set__ = require("../lib/__set__.js"), 3 | vm = require("vm"), 4 | 5 | expectTypeError = expectError(TypeError); 6 | 7 | function expectError(ErrConstructor) { 8 | return function expectReferenceError(err) { 9 | expect(err.constructor.name).to.be(ErrConstructor.name); 10 | }; 11 | } 12 | 13 | describe("__set__", function () { 14 | var moduleFake, 15 | undo; 16 | 17 | beforeEach(function () { 18 | moduleFake = { 19 | module: { 20 | exports: {} 21 | }, 22 | myValue: 0, // copy by value 23 | myReference: {} // copy by reference 24 | }; 25 | 26 | vm.runInNewContext( 27 | //__set__ requires __set__ to be present on module.exports 28 | "__set__ = module.exports.__set__ = " + __set__.toString() + "; " + 29 | "getValue = function () { return myValue; }; " + 30 | "getReference = function () { return myReference; }; ", 31 | moduleFake 32 | ); 33 | }); 34 | it("should set the new value when calling with varName, varValue", function () { 35 | expect(moduleFake.getValue()).to.be(0); 36 | moduleFake.__set__("myValue", undefined); 37 | expect(moduleFake.getValue()).to.be(undefined); 38 | moduleFake.__set__("myValue", null); 39 | expect(moduleFake.getValue()).to.be(null); 40 | moduleFake.__set__("myValue", 2); 41 | expect(moduleFake.getValue()).to.be(2); 42 | moduleFake.__set__("myValue", "hello"); 43 | expect(moduleFake.getValue()).to.be("hello"); 44 | }); 45 | it("should set the new reference when calling with varName, varValue", function () { 46 | var newObj = { hello: "hello" }, 47 | newArr = [1, 2, 3], 48 | regExp = /123/gi; 49 | 50 | function newFn() { 51 | console.log("hello"); 52 | } 53 | 54 | expect(moduleFake.getReference()).to.eql({}); 55 | moduleFake.__set__("myReference", newObj); 56 | expect(moduleFake.getReference()).to.be(newObj); 57 | moduleFake.__set__("myReference", newArr); 58 | expect(moduleFake.getReference()).to.be(newArr); 59 | moduleFake.__set__("myReference", newFn); 60 | expect(moduleFake.getReference()).to.be(newFn); 61 | moduleFake.__set__("myReference", regExp); 62 | expect(moduleFake.getReference()).to.be(regExp); 63 | }); 64 | it("should set the new number and the new obj when calling with an env-obj", function () { 65 | var newObj = { hello: "hello" }; 66 | 67 | expect(moduleFake.getValue()).to.be(0); 68 | expect(moduleFake.getReference()).to.eql({}); 69 | moduleFake.__set__({ 70 | myValue: 2, 71 | myReference: newObj 72 | }); 73 | expect(moduleFake.getValue()).to.be(2); 74 | expect(moduleFake.getReference()).to.be(newObj); 75 | }); 76 | it("should return a function that when invoked reverts to the values before set was called", function () { 77 | undo = moduleFake.__set__("myValue", 4); 78 | expect(undo).to.be.a("function"); 79 | expect(moduleFake.getValue()).to.be(4); 80 | undo(); 81 | expect(moduleFake.getValue()).to.be(0); 82 | }); 83 | it("should be able to revert when calling with an env-obj", function () { 84 | var newObj = { hello: "hello" }; 85 | 86 | expect(moduleFake.getValue()).to.be(0); 87 | expect(moduleFake.getReference()).to.eql({}); 88 | 89 | undo = moduleFake.__set__({ 90 | myValue: 2, 91 | myReference: newObj 92 | }); 93 | 94 | expect(moduleFake.getValue()).to.be(2); 95 | expect(moduleFake.getReference()).to.be(newObj); 96 | 97 | undo(); 98 | 99 | expect(moduleFake.getValue()).to.be(0); 100 | expect(moduleFake.getReference()).to.eql({}); 101 | }); 102 | it("should throw a TypeError when passing misfitting params", function () { 103 | expect(function () { 104 | moduleFake.__set__(); 105 | }).to.throwException(expectTypeError); 106 | expect(function () { 107 | moduleFake.__set__(undefined); 108 | }).to.throwException(expectTypeError); 109 | expect(function () { 110 | moduleFake.__set__(null); 111 | }).to.throwException(expectTypeError); 112 | expect(function () { 113 | moduleFake.__set__(true); 114 | }).to.throwException(expectTypeError); 115 | expect(function () { 116 | moduleFake.__set__(2); 117 | }).to.throwException(expectTypeError); 118 | expect(function () { 119 | moduleFake.__set__(""); 120 | }).to.throwException(expectTypeError); 121 | expect(function () { 122 | moduleFake.__set__(function () {}); 123 | }).to.throwException(expectTypeError); 124 | }); 125 | }); 126 | -------------------------------------------------------------------------------- /test/__with__.test.js: -------------------------------------------------------------------------------- 1 | var expect = require("expect.js"), 2 | __with__ = require("../lib/__with__.js"), 3 | __set__ = require("../lib/__set__.js"), 4 | vm = require("vm"), 5 | 6 | expectTypeError = expectError(TypeError); 7 | 8 | function expectError(ErrConstructor) { 9 | return function expectReferenceError(err) { 10 | expect(err.constructor.name).to.be(ErrConstructor.name); 11 | }; 12 | } 13 | 14 | describe("__with__", function() { 15 | var moduleFake, 16 | newObj; 17 | 18 | beforeEach(function () { 19 | moduleFake = { 20 | module: { 21 | exports: {} 22 | }, 23 | myValue: 0, // copy by value 24 | myReference: {} // copy by reference 25 | }; 26 | 27 | newObj = { hello: "hello" }; 28 | 29 | vm.runInNewContext( 30 | //__with__ requires __set__ to be present on module.exports 31 | "module.exports.__set__ = " + __set__.toString() + "; " + 32 | "__with__ = " + __with__.toString() + "; " + 33 | "getValue = function () { return myValue; }; " + 34 | "getReference = function () { return myReference; }; ", 35 | moduleFake 36 | ); 37 | }); 38 | 39 | it("should return a function", function () { 40 | expect(moduleFake.__with__({ 41 | myValue: 2, 42 | myReference: newObj 43 | })).to.be.a("function"); 44 | }); 45 | 46 | it("should return a function that can be invoked with a callback which guarantees __set__'s undo function is called for you at the end", function () { 47 | expect(moduleFake.getValue()).to.be(0); 48 | expect(moduleFake.getReference()).to.eql({}); 49 | 50 | moduleFake.__with__({ 51 | myValue: 2, 52 | myReference: newObj 53 | })(function () { 54 | // changes will be visible from within this callback function 55 | expect(moduleFake.getValue()).to.be(2); 56 | expect(moduleFake.getReference()).to.be(newObj); 57 | }); 58 | 59 | // undo will automatically get called for you after returning from your callback function 60 | expect(moduleFake.getValue()).to.be(0); 61 | expect(moduleFake.getReference()).to.eql({}); 62 | }); 63 | 64 | it("should also accept a variable name and a variable value (just like __set__)", function () { 65 | expect(moduleFake.getValue()).to.be(0); 66 | 67 | moduleFake.__with__("myValue", 2)(function () { 68 | expect(moduleFake.getValue()).to.be(2); 69 | }); 70 | 71 | expect(moduleFake.getValue()).to.be(0); 72 | 73 | expect(moduleFake.getReference()).to.eql({}); 74 | 75 | moduleFake.__with__("myReference", newObj)(function () { 76 | expect(moduleFake.getReference()).to.be(newObj); 77 | }); 78 | 79 | expect(moduleFake.getReference()).to.eql({}); 80 | }); 81 | 82 | it("should still revert values if the callback throws an exception", function(){ 83 | expect(function withError() { 84 | moduleFake.__with__({ 85 | myValue: 2, 86 | myReference: newObj 87 | })(function () { 88 | throw new Error("something went wrong..."); 89 | }); 90 | }).to.throwError(); 91 | expect(moduleFake.getValue()).to.be(0); 92 | expect(moduleFake.getReference()).to.eql({}); 93 | }); 94 | 95 | it("should throw an error if something other than a function is passed as the callback", function() { 96 | var withFunction = moduleFake.__with__({ 97 | myValue: 2, 98 | myReference: newObj 99 | }); 100 | 101 | function callWithFunction() { 102 | var args = arguments; 103 | 104 | return function () { 105 | withFunction.apply(null, args); 106 | }; 107 | } 108 | 109 | expect(callWithFunction(1)).to.throwError(expectTypeError); 110 | expect(callWithFunction("a string")).to.throwError(expectTypeError); 111 | expect(callWithFunction({})).to.throwError(expectTypeError); 112 | expect(callWithFunction(function(){})).to.not.throwError(expectTypeError); 113 | }); 114 | 115 | describe("using promises", function () { 116 | var promiseFake; 117 | 118 | beforeEach(function () { 119 | promiseFake = { 120 | then: function (onResolve, onReject) { 121 | promiseFake.onResolve = onResolve; 122 | promiseFake.onReject = onReject; 123 | } 124 | }; 125 | }); 126 | 127 | it("should pass the returned promise through", function () { 128 | var fn = moduleFake.__with__({}); 129 | 130 | expect(fn(function () { 131 | return promiseFake; 132 | })).to.equal(promiseFake); 133 | }); 134 | 135 | it("should not undo any changes until the promise has been resolved", function () { 136 | expect(moduleFake.getValue()).to.be(0); 137 | expect(moduleFake.getReference()).to.eql({}); 138 | 139 | moduleFake.__with__({ 140 | myValue: 2, 141 | myReference: newObj 142 | })(function () { 143 | return promiseFake; 144 | }); 145 | 146 | // the change should still be present at this point 147 | expect(moduleFake.getValue()).to.be(2); 148 | expect(moduleFake.getReference()).to.be(newObj); 149 | 150 | promiseFake.onResolve(); 151 | 152 | // now everything should be back to normal 153 | expect(moduleFake.getValue()).to.be(0); 154 | expect(moduleFake.getReference()).to.eql({}); 155 | }); 156 | 157 | it("should also undo any changes if the promise has been rejected", function () { 158 | expect(moduleFake.getValue()).to.be(0); 159 | expect(moduleFake.getReference()).to.eql({}); 160 | 161 | moduleFake.__with__({ 162 | myValue: 2, 163 | myReference: newObj 164 | })(function () { 165 | return promiseFake; 166 | }); 167 | 168 | // the change should still be present at this point 169 | expect(moduleFake.getValue()).to.be(2); 170 | expect(moduleFake.getReference()).to.be(newObj); 171 | 172 | promiseFake.onReject(); 173 | 174 | // now everything should be back to normal 175 | expect(moduleFake.getValue()).to.be(0); 176 | expect(moduleFake.getReference()).to.eql({}); 177 | }); 178 | 179 | it("should ignore any returned value which doesn't provide a then()-method", function () { 180 | expect(moduleFake.getValue()).to.be(0); 181 | expect(moduleFake.getReference()).to.eql({}); 182 | 183 | moduleFake.__with__({ 184 | myValue: 2, 185 | myReference: newObj 186 | })(function () { 187 | return {}; 188 | }); 189 | 190 | expect(moduleFake.getValue()).to.be(0); 191 | expect(moduleFake.getReference()).to.eql({}); 192 | }); 193 | 194 | }); 195 | 196 | }); 197 | -------------------------------------------------------------------------------- /test/detectStrictMode.test.js: -------------------------------------------------------------------------------- 1 | var expect = require("expect.js"), 2 | detectStrictMode = require("../lib/detectStrictMode.js"); 3 | 4 | describe("detectStrictMode", function () { 5 | 6 | it("should detect all valid uses of \"use strict\";", function () { 7 | expect(detectStrictMode('"use strict";')).to.be(true); 8 | expect(detectStrictMode("'use strict';")).to.be(true); 9 | expect(detectStrictMode(' "use strict";')).to.be(true); 10 | expect(detectStrictMode('\n"use strict";')).to.be(true); 11 | expect(detectStrictMode('\r\n"use strict";')).to.be(true); 12 | expect(detectStrictMode('"use strict"\r\n')).to.be(true); 13 | expect(detectStrictMode('"use strict" ; test();')).to.be(true); 14 | }); 15 | 16 | it("should be allowed to place comments before \"use strict\";", function () { 17 | expect(detectStrictMode('// some comment\n"use strict";')).to.be(true); 18 | expect(detectStrictMode('/* yo! */"use strict"; /* another comment */')).to.be(true); 19 | expect(detectStrictMode(' // yes yo\r\n\r\n\r\n /*oh yoh*/\r\n//oh snap!\r /* yoh! */"use strict";')).to.be(true); 20 | }); 21 | 22 | it("should not detect invalid uses of \"use strict\";", function () { 23 | expect(detectStrictMode('" use strict ";')).to.be(false); 24 | expect(detectStrictMode('"use strict".replace("use", "fail");')).to.be(false); 25 | expect(detectStrictMode('"use strict" .replace("use", "fail");')).to.be(false); 26 | expect(detectStrictMode(';"use strict";')).to.be(false); 27 | }); 28 | 29 | it("should not detect \"use strict\"; if it occurs in some nested function", function () { 30 | expect(detectStrictMode('function () {"use strict";}')).to.be(false); 31 | }); 32 | 33 | }); 34 | -------------------------------------------------------------------------------- /test/getImportGlobalsSrc.test.js: -------------------------------------------------------------------------------- 1 | var expect = require("expect.js"), 2 | vm = require("vm"), 3 | getImportGlobalsSrc = require("../lib/getImportGlobalsSrc.js"); 4 | 5 | describe("getImportGlobalsSrc", function () { 6 | 7 | it("should declare all globals with a var", function () { 8 | var context = { 9 | global: global 10 | }, 11 | expectedGlobals, 12 | src, 13 | actualGlobals; 14 | 15 | // Temporarily set module-internal variables on the global scope to check if getImportGlobalsSrc() 16 | // ignores them properly 17 | global.module = module; 18 | global.exports = exports; 19 | global.require = require; 20 | 21 | // Also make sure it ignores invalid variable names 22 | global['a-b'] = true; 23 | 24 | src = getImportGlobalsSrc(); 25 | 26 | delete global.module; 27 | delete global.exports; 28 | delete global.require; 29 | delete global['__core-js_shared__']; 30 | delete global['a-b']; 31 | 32 | const ignoredGlobals = ["module", "exports", "require", "undefined", "eval", "arguments", "GLOBAL", "root", "NaN", "Infinity"]; 33 | 34 | const globals = Object.getOwnPropertyNames(global); 35 | expectedGlobals = globals.filter((el) => !ignoredGlobals.includes(el)); 36 | 37 | vm.runInNewContext(src, context); 38 | actualGlobals = Object.getOwnPropertyNames(context); 39 | 40 | actualGlobals.sort(); 41 | expectedGlobals.sort(); 42 | expect(actualGlobals).to.eql(expectedGlobals); 43 | expect(actualGlobals.length).to.be.above(1); 44 | }); 45 | 46 | it("should ignore the given variables", function () { 47 | var context = { 48 | global: global 49 | }, 50 | ignore = ["console", "setTimeout"], 51 | src, 52 | actualGlobals, 53 | expectedGlobals = Object.getOwnPropertyNames(global); 54 | 55 | const ignoredGlobals = ["module", "exports", "require", "undefined", "eval", "arguments", "GLOBAL", "root", "NaN", "Infinity"]; 56 | ignore = ignore.concat(ignoredGlobals); 57 | 58 | // getImportGlobalsSrc modifies the ignore array, so let's create a copy 59 | src = getImportGlobalsSrc(ignore.slice(0)); 60 | expectedGlobals = expectedGlobals.filter((el) => !ignore.includes(el)); 61 | 62 | vm.runInNewContext(src, context); 63 | actualGlobals = Object.keys(context); 64 | 65 | actualGlobals.sort(); 66 | expectedGlobals.sort(); 67 | expect(actualGlobals).to.eql(expectedGlobals); 68 | expect(actualGlobals.length).to.be.above(1); 69 | }); 70 | 71 | }); 72 | -------------------------------------------------------------------------------- /test/rewire.test.js: -------------------------------------------------------------------------------- 1 | // Don't run code in ES5 strict mode. 2 | // In case this module was in strict mode, all other modules called by this would also be strict. 3 | // But when testing if the strict mode is preserved, we must ensure that this module is NOT strict. 4 | 5 | var expect = require("expect.js"), 6 | fs = require("fs"), 7 | path = require("path"); 8 | 9 | var rewire; 10 | 11 | describe("rewire", function () { 12 | before(function () { 13 | var fakeNodeModules = path.resolve(__dirname, "../testLib/fake_node_modules"); 14 | 15 | if (fs.existsSync(fakeNodeModules)) { 16 | fs.renameSync(fakeNodeModules, path.resolve(__dirname, "../testLib/node_modules")); 17 | } 18 | }); 19 | require("../testLib/sharedTestCases.js")(); 20 | it("should work with TypeScript", function () { 21 | var tsModule; 22 | rewire = require("../"); 23 | tsModule = rewire("../testLib/module.ts"); 24 | tsModule.__set__("fs", { 25 | readFileSync: function () { 26 | return "It works!"; 27 | } 28 | }); 29 | expect(tsModule.readFileSync()).to.be("It works!"); 30 | }); 31 | }); 32 | -------------------------------------------------------------------------------- /testLib/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["es2015"], 3 | "plugins": ["transform-class-properties"] 4 | } 5 | -------------------------------------------------------------------------------- /testLib/boolean.js: -------------------------------------------------------------------------------- 1 | module.exports = true; 2 | -------------------------------------------------------------------------------- /testLib/constModule.js: -------------------------------------------------------------------------------- 1 | const j = "j"; // At the beginning of the file 2 | // This module contains some weird combinations where valid const declarations could appear. 3 | // Syntax oddities are totally on purpose here. 4 | const a = require("./someOtherModule");const b = "b"; const e = "e" 5 | const c = "c"; 6 | {}const d = "d"; 7 |  const f = "f"; // there's an irregular whitespace before and after const 8 | const 9 | g = "g"; 10 | const/*wtf this is valid*/h = "h"; 11 | const /*and this is also*/i = "i"; 12 | const{k} = {k: "k"}; 13 | 14 | exports.a = function () { 15 | return a; 16 | }; 17 | exports.b = function () { 18 | return b; 19 | }; 20 | exports.c = function () { 21 | return c; 22 | }; 23 | exports.d = function () { 24 | return d; 25 | }; 26 | exports.e = function () { 27 | return e; 28 | }; 29 | exports.f = function () { 30 | return f; 31 | }; 32 | exports.g = function () { 33 | return g; 34 | }; 35 | exports.h = function () { 36 | return h; 37 | }; 38 | exports.i = function () { 39 | return i; 40 | }; 41 | exports.j = function () { 42 | return j; 43 | }; 44 | exports.k = function () { 45 | return k; 46 | }; 47 | -------------------------------------------------------------------------------- /testLib/debuggerModule.js: -------------------------------------------------------------------------------- 1 | "use strict"; // run code in ES5 strict mode 2 | 3 | var myNumber = 0; 4 | 5 | module.exports = function () { 6 | myNumber = 1; 7 | }; -------------------------------------------------------------------------------- /testLib/emptyModule.js: -------------------------------------------------------------------------------- 1 | "use strict"; // run code in ES5 strict mode 2 | 3 | var someVar; 4 | 5 | // Comment on file end. Hope this won't break anything -------------------------------------------------------------------------------- /testLib/implicitGlobal.js: -------------------------------------------------------------------------------- 1 | implicitGlobal = "this is an implicit global var ..." + 2 | "yes, it's bad coding style but there are still some libs out there"; 3 | 4 | module.exports = function () { 5 | return undefinedImplicitGlobal; 6 | }; 7 | -------------------------------------------------------------------------------- /testLib/module.ts: -------------------------------------------------------------------------------- 1 | import * as fs from 'fs'; 2 | 3 | export function readFileSync(path: string) { 4 | return fs.readFileSync(path); 5 | } 6 | -------------------------------------------------------------------------------- /testLib/moduleA.js: -------------------------------------------------------------------------------- 1 | "use strict"; // run code in ES5 strict mode 2 | 3 | var someOtherModule = require("./someOtherModule.js"), 4 | myNumber = 0, // copy by value 5 | myObj = {}, // copy by reference 6 | env = "bla", 7 | fs; 8 | 9 | // We need getters and setters for private vars to check if our injected setters and getters actual work 10 | function setMyNumber(newNumber) { 11 | myNumber = newNumber; 12 | } 13 | 14 | function getMyNumber() { 15 | return myNumber; 16 | } 17 | 18 | function setMyObj(newObj) { 19 | myObj = newObj; 20 | } 21 | 22 | function getMyObj() { 23 | return myObj; 24 | } 25 | 26 | function readFileSync() { 27 | fs.readFileSync("bla.txt", "utf8"); 28 | } 29 | 30 | function checkSomeGlobals() { 31 | var isLowerIE, 32 | typeOfGlobalFunc; 33 | 34 | if (typeof navigator !== "undefined") { 35 | isLowerIE = /MSIE [6-8]\.[0-9]/g.test(navigator.userAgent); 36 | } 37 | if (isLowerIE) { 38 | typeOfGlobalFunc = "object"; 39 | } else { 40 | typeOfGlobalFunc = "function"; 41 | } 42 | 43 | if (typeof global !== "object") { 44 | throw new ReferenceError("global is not an object"); 45 | } 46 | if (typeof console !== "object") { 47 | throw new ReferenceError("console is not an object"); 48 | } 49 | if (typeof require !== "function") { 50 | throw new ReferenceError("require is not a function"); 51 | } 52 | if (typeof module !== "object") { 53 | throw new ReferenceError("module is not an object"); 54 | } 55 | if (typeof exports !== "object") { 56 | throw new ReferenceError("exports is not an object"); 57 | } 58 | if (module.exports !== exports) { 59 | throw new Error("module.exports === exports returns false"); 60 | } 61 | if (typeof __dirname !== "string") { 62 | throw new ReferenceError("__dirname is not a string"); 63 | } 64 | if (typeof __filename !== "string") { 65 | throw new ReferenceError("__filename is not a string"); 66 | } 67 | if (typeof setTimeout !== typeOfGlobalFunc) { 68 | throw new ReferenceError("setTimeout is not a function"); 69 | } 70 | if (typeof clearTimeout !== typeOfGlobalFunc) { 71 | throw new ReferenceError("clearTimeout is not a function"); 72 | } 73 | if (typeof setInterval !== typeOfGlobalFunc) { 74 | throw new ReferenceError("setInterval is not a function"); 75 | } 76 | if (typeof clearInterval !== typeOfGlobalFunc) { 77 | throw new ReferenceError("clearInterval is not a function"); 78 | } 79 | if (typeof Error !== "function") { 80 | throw new ReferenceError("Error is not a function"); 81 | } 82 | if (typeof parseFloat !== "function") { 83 | throw new ReferenceError("parseFloat is not a function"); 84 | } 85 | if (typeof parseInt !== "function") { 86 | throw new ReferenceError("parseInt is not a function"); 87 | } 88 | if (typeof window === "undefined") { 89 | if (typeof process !== "object") { 90 | throw new ReferenceError("process is not an object"); 91 | } 92 | if (typeof Buffer !== "function") { 93 | throw new ReferenceError("Buffer is not a function"); 94 | } 95 | } else { 96 | if (typeof encodeURIComponent !== "function") { 97 | throw new ReferenceError("encodeURIComponent is not a function"); 98 | } 99 | if (typeof decodeURIComponent !== "function") { 100 | throw new ReferenceError("decodeURIComponent is not a function"); 101 | } 102 | if (typeof document !== "object") { 103 | throw new ReferenceError("document is not an object"); 104 | } 105 | } 106 | } 107 | 108 | function getConsole() { 109 | return console; 110 | } 111 | 112 | function getFilename() { 113 | return __filename; 114 | } 115 | 116 | function getBuffer() { 117 | return Buffer; 118 | } 119 | 120 | function getDocument() { 121 | return document; 122 | } 123 | 124 | // different styles of exports in moduleA.js and moduleB.js 125 | exports.setMyNumber = setMyNumber; 126 | exports.getMyNumber = getMyNumber; 127 | exports.setMyObj = setMyObj; 128 | exports.getMyObj = getMyObj; 129 | exports.readFileSync = readFileSync; 130 | exports.checkSomeGlobals = checkSomeGlobals; 131 | exports.getConsole = getConsole; 132 | exports.getFilename = getFilename; 133 | exports.getBuffer = getBuffer; 134 | exports.getDocument = getDocument; 135 | exports.someOtherModule = someOtherModule; -------------------------------------------------------------------------------- /testLib/moduleB.js: -------------------------------------------------------------------------------- 1 | "use strict"; // run code in ES5 strict mode 2 | 3 | var someOtherModule = require("./someOtherModule.js"), 4 | myNumber = 0, // copy by value 5 | myObj = {}, // copy by reference 6 | env = "bla", 7 | fs; 8 | 9 | // We need getters and setters for private vars to check if our injected setters and getters actual work 10 | function setMyNumber(newNumber) { 11 | myNumber = newNumber; 12 | } 13 | 14 | function getMyNumber() { 15 | return myNumber; 16 | } 17 | 18 | function setMyObj(newObj) { 19 | myObj = newObj; 20 | } 21 | 22 | function getMyObj() { 23 | return myObj; 24 | } 25 | 26 | function readFileSync() { 27 | fs.readFileSync("bla.txt", "utf8"); 28 | } 29 | 30 | function checkSomeGlobals() { 31 | var isLowerIE, 32 | typeOfGlobalFunc; 33 | 34 | if (typeof navigator !== "undefined") { 35 | isLowerIE = /MSIE [6-8]\.[0-9]/g.test(navigator.userAgent); 36 | } 37 | if (isLowerIE) { 38 | typeOfGlobalFunc = "object"; 39 | } else { 40 | typeOfGlobalFunc = "function"; 41 | } 42 | 43 | if (typeof global !== "object") { 44 | throw new ReferenceError("global is not an object"); 45 | } 46 | if (typeof console !== "object") { 47 | throw new ReferenceError("console is not an object"); 48 | } 49 | if (typeof require !== "function") { 50 | throw new ReferenceError("require is not a function"); 51 | } 52 | if (typeof module !== "object") { 53 | throw new ReferenceError("module is not an object"); 54 | } 55 | if (typeof exports !== "object") { 56 | throw new ReferenceError("exports is not an object"); 57 | } 58 | if (module.exports === exports) { 59 | throw new Error("module.exports === exports returns true"); 60 | } 61 | if (typeof __dirname !== "string") { 62 | throw new ReferenceError("__dirname is not a string"); 63 | } 64 | if (typeof __filename !== "string") { 65 | throw new ReferenceError("__filename is not a string"); 66 | } 67 | if (typeof setTimeout !== typeOfGlobalFunc) { 68 | throw new ReferenceError("setTimeout is not a function"); 69 | } 70 | if (typeof clearTimeout !== typeOfGlobalFunc) { 71 | throw new ReferenceError("clearTimeout is not a function"); 72 | } 73 | if (typeof setInterval !== typeOfGlobalFunc) { 74 | throw new ReferenceError("setInterval is not a function"); 75 | } 76 | if (typeof clearInterval !== typeOfGlobalFunc) { 77 | throw new ReferenceError("clearInterval is not a function"); 78 | } 79 | if (typeof Error !== "function") { 80 | throw new ReferenceError("Error is not a function"); 81 | } 82 | if (typeof parseFloat !== "function") { 83 | throw new ReferenceError("parseFloat is not a function"); 84 | } 85 | if (typeof parseInt !== "function") { 86 | throw new ReferenceError("parseInt is not a function"); 87 | } 88 | if (typeof window === "undefined") { 89 | if (typeof process !== "object") { 90 | throw new ReferenceError("process is not an object"); 91 | } 92 | if (typeof Buffer !== "function") { 93 | throw new ReferenceError("Buffer is not a function"); 94 | } 95 | } else { 96 | if (typeof encodeURIComponent !== "function") { 97 | throw new ReferenceError("encodeURIComponent is not a function"); 98 | } 99 | if (typeof decodeURIComponent !== "function") { 100 | throw new ReferenceError("decodeURIComponent is not a function"); 101 | } 102 | if (typeof document !== "object") { 103 | throw new ReferenceError("document is not an object"); 104 | } 105 | } 106 | } 107 | 108 | function getConsole() { 109 | return console; 110 | } 111 | 112 | function getFilename() { 113 | return __filename; 114 | } 115 | 116 | function getBuffer() { 117 | return Buffer; 118 | } 119 | 120 | function getDocument() { 121 | return document; 122 | } 123 | 124 | // different styles of exports in moduleA.js and moduleB.js 125 | module.exports = { 126 | setMyNumber: setMyNumber, 127 | getMyNumber: getMyNumber, 128 | setMyObj: setMyObj, 129 | getMyObj: getMyObj, 130 | readFileSync: readFileSync, 131 | checkSomeGlobals: checkSomeGlobals, 132 | getConsole: getConsole, 133 | getFilename: getFilename, 134 | getBuffer: getBuffer, 135 | getDocument: getDocument, 136 | someOtherModule: someOtherModule 137 | }; 138 | -------------------------------------------------------------------------------- /testLib/node_modules/rewire/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "main": "../../../lib/index.js" 3 | } 4 | -------------------------------------------------------------------------------- /testLib/null.js: -------------------------------------------------------------------------------- 1 | module.exports = null; 2 | -------------------------------------------------------------------------------- /testLib/objectRestOperator.js: -------------------------------------------------------------------------------- 1 | let { ...a } = {}; 2 | module.exports = a; 3 | -------------------------------------------------------------------------------- /testLib/objectSpreadOperator.js: -------------------------------------------------------------------------------- 1 | module.exports = { ...{} }; 2 | -------------------------------------------------------------------------------- /testLib/sealedObject.js: -------------------------------------------------------------------------------- 1 | var obj = {}; 2 | Object.seal(obj); 3 | 4 | module.exports = obj; 5 | -------------------------------------------------------------------------------- /testLib/sharedTestCases.js: -------------------------------------------------------------------------------- 1 | // Don't run code in ES5 strict mode. 2 | // In case this module was in strict mode, all other modules called by this would also be strict. 3 | // But when testing if the strict mode is preserved, we must ensure that this module is NOT strict. 4 | 5 | // These shared test cases are used to check if the provided implementation of rewire is compatible 6 | // with the original rewire. Since you can use rewire with client-side bundlers like webpack we need 7 | // to test the implementation there again. 8 | // @see https://github.com/jhnns/rewire-webpack 9 | 10 | var expect = require("expect.js"), 11 | rewire = require("rewire"), 12 | __set__Src = require("../lib/__set__.js").toString(), 13 | __get__Src = require("../lib/__get__.js").toString(), 14 | __with__Src = require("../lib/__with__.js").toString(); 15 | 16 | var supportsObjectSpread = (function () { 17 | try { 18 | eval("({...{}})"); 19 | return true; 20 | } catch (err) { 21 | return false; 22 | } 23 | })(); 24 | var supportsObjectRest = (function () { 25 | try { 26 | eval("const {...a} = {}"); 27 | return true; 28 | } catch (err) { 29 | return false; 30 | } 31 | })(); 32 | 33 | function checkForTypeError(err) { 34 | expect(err.constructor).to.be(TypeError); 35 | } 36 | 37 | module.exports = function () { 38 | 39 | it("should work like require()", function () { 40 | rewire("./moduleA.js").getFilename(); 41 | require("./moduleA.js").getFilename(); 42 | expect(rewire("./moduleA.js").getFilename()).to.eql(require("./moduleA.js").getFilename()); 43 | expect(rewire("../testLib/someOtherModule.js").filename).to.eql(require("../testLib/someOtherModule.js").filename); 44 | }); 45 | 46 | it("should return a fresh instance of the module", function () { 47 | var someOtherModule = require("./someOtherModule.js"), 48 | rewiredSomeOtherModule; 49 | 50 | someOtherModule.fs = "This has been modified"; 51 | rewiredSomeOtherModule = rewire("./someOtherModule.js"); 52 | expect(rewiredSomeOtherModule.fs).not.to.be("This has been modified"); 53 | }); 54 | 55 | it("should not cache the rewired module", function () { 56 | var rewired, 57 | someOtherModule = require("./someOtherModule.js"); 58 | 59 | someOtherModule.fs = "This has been changed"; 60 | 61 | rewired = rewire("./someOtherModule.js"); 62 | expect(someOtherModule).not.to.be(rewired); 63 | expect(require("./moduleA.js").someOtherModule).not.to.be(rewired); 64 | expect(require("./moduleA.js").someOtherModule).to.be(someOtherModule); 65 | expect(require("./moduleA.js").someOtherModule.fs).to.be("This has been changed"); 66 | }); 67 | 68 | // By comparing the src we can ensure that the provided __set__ function is our tested implementation 69 | it("should modify the module so it provides the __set__ - function", function () { 70 | expect(rewire("./moduleA.js").__set__.toString()).to.be(__set__Src); 71 | expect(rewire("./moduleB.js").__set__.toString()).to.be(__set__Src); 72 | }); 73 | 74 | // By comparing the src we can ensure that the provided __set__ function is our tested implementation 75 | it("should modify the module so it provides the __get__ - function", function () { 76 | expect(rewire("./moduleA.js").__get__.toString()).to.be(__get__Src); 77 | expect(rewire("./moduleB.js").__get__.toString()).to.be(__get__Src); 78 | }); 79 | 80 | // By comparing the src we can ensure that the provided __set__ function is our tested implementation 81 | it("should modify the module so it provides the __with__ - function", function () { 82 | expect(rewire("./moduleA.js").__with__.toString()).to.be(__with__Src); 83 | expect(rewire("./moduleB.js").__with__.toString()).to.be(__with__Src); 84 | }); 85 | 86 | 87 | ["__get__", "__set__", "__with__"].forEach(function(funcName) { 88 | it("should provide " + funcName + " as a non-enumerable property", function () { 89 | expect(Object.keys(rewire("./moduleA.js")).indexOf(funcName)).to.be(-1); 90 | }); 91 | 92 | it("should provide " + funcName + " as a writable property", function () { 93 | var obj = rewire("./moduleA.js"); 94 | var desc = Object.getOwnPropertyDescriptor(obj, funcName); 95 | expect(desc.writable).to.be(true); 96 | }); 97 | }); 98 | 99 | it("should not influence other modules", function () { 100 | rewire("./moduleA.js"); 101 | 102 | expect(require("./someOtherModule.js").__set__).to.be(undefined); 103 | expect(require("./someOtherModule.js").__get__).to.be(undefined); 104 | expect(require("./someOtherModule.js").__with__).to.be(undefined); 105 | }); 106 | 107 | it("should not override/influence global objects by default", function () { 108 | // This should throw no exception 109 | rewire("./moduleA.js").checkSomeGlobals(); 110 | rewire("./moduleB.js").checkSomeGlobals(); 111 | }); 112 | 113 | // This is just an integration test for the __set__ method 114 | // You can find a full test for __set__ under /test/__set__.test.js 115 | it("should provide a working __set__ method", function () { 116 | var rewiredModuleA = rewire("./moduleA.js"), 117 | newObj = {}; 118 | 119 | expect(rewiredModuleA.getMyNumber()).to.be(0); 120 | rewiredModuleA.__set__("myNumber", 2); 121 | expect(rewiredModuleA.getMyNumber()).to.be(2); 122 | rewiredModuleA.__set__("myObj", newObj); 123 | expect(rewiredModuleA.getMyObj()).to.be(newObj); 124 | rewiredModuleA.__set__("env", "ENVENV"); 125 | }); 126 | 127 | // This is just an integration test for the __get__ method 128 | // You can find a full test for __get__ under /test/__get__.test.js 129 | it("should provide a working __get__ method", function () { 130 | var rewiredModuleA = rewire("./moduleA.js"); 131 | 132 | expect(rewiredModuleA.__get__("myNumber")).to.be(rewiredModuleA.getMyNumber()); 133 | expect(rewiredModuleA.__get__("myObj")).to.be(rewiredModuleA.getMyObj()); 134 | }); 135 | 136 | // This is just an integration test for the __with__ method 137 | // You can find a full test for __with__ under /test/__with__.test.js 138 | it("should provide a working __with__ method", function () { 139 | var rewiredModuleA = rewire("./moduleA.js"), 140 | newObj = {}; 141 | 142 | expect(rewiredModuleA.getMyNumber()).to.be(0); 143 | expect(rewiredModuleA.getMyObj()).to.not.be(newObj); 144 | 145 | rewiredModuleA.__with__({ 146 | myNumber: 2, 147 | myObj: newObj 148 | })(function () { 149 | expect(rewiredModuleA.getMyNumber()).to.be(2); 150 | expect(rewiredModuleA.getMyObj()).to.be(newObj); 151 | }); 152 | 153 | expect(rewiredModuleA.getMyNumber()).to.be(0); 154 | expect(rewiredModuleA.getMyObj()).to.not.be(newObj); 155 | }); 156 | 157 | it("should provide the ability to inject mocks", function (done) { 158 | var rewiredModuleA = rewire("./moduleA.js"), 159 | mockedFs = { 160 | readFileSync: function (file) { 161 | expect(file).to.be("bla.txt"); 162 | done(); 163 | } 164 | }; 165 | 166 | rewiredModuleA.__set__("fs", mockedFs); 167 | rewiredModuleA.readFileSync(); 168 | }); 169 | 170 | it("should not influence other modules when injecting mocks", function () { 171 | var rewiredModuleA = rewire("./moduleA.js"), 172 | someOtherModule, 173 | mockedFs = {}; 174 | 175 | rewiredModuleA.__set__("fs", mockedFs); 176 | someOtherModule = require("./someOtherModule.js"); 177 | expect(someOtherModule.fs).not.to.be(mockedFs); 178 | }); 179 | 180 | it("should provide the ability to mock global objects just within the module", function () { 181 | var rewiredModuleA = rewire("./moduleA.js"), 182 | rewiredModuleB = rewire("./moduleB.js"), 183 | consoleMock = {}, 184 | bufferMock = {}, 185 | documentMock = {}, 186 | newFilename = "myFile.js"; 187 | 188 | rewiredModuleA.__set__({ 189 | console: consoleMock, 190 | __filename: newFilename 191 | }); 192 | expect(rewiredModuleA.getConsole()).to.be(consoleMock); 193 | expect(rewiredModuleB.getConsole()).not.to.be(consoleMock); 194 | expect(console).not.to.be(consoleMock); 195 | expect(rewiredModuleA.getFilename()).to.be(newFilename); 196 | expect(rewiredModuleB.getFilename()).not.to.be(newFilename); 197 | expect(console).not.to.be(newFilename); 198 | if (typeof window === "undefined") { 199 | rewiredModuleA.__set__("Buffer", bufferMock); 200 | expect(rewiredModuleA.getBuffer()).to.be(bufferMock); 201 | expect(rewiredModuleB.getBuffer()).not.to.be(bufferMock); 202 | expect(Buffer).not.to.be(bufferMock); 203 | } else { 204 | rewiredModuleA.__set__("document", documentMock); 205 | expect(rewiredModuleA.getDocument()).to.be(documentMock); 206 | expect(rewiredModuleB.getDocument() === documentMock).to.be(false); 207 | expect(document === documentMock).to.be(false); 208 | } 209 | }); 210 | 211 | it("should be possible to mock global objects that are added on runtime", function () { 212 | var rewiredModule; 213 | 214 | if (typeof window === "undefined") { 215 | global.someGlobalVar = "test"; 216 | rewiredModule = rewire("./moduleA.js"); 217 | rewiredModule.__set__("someGlobalVar", "other value"); 218 | expect(global.someGlobalVar).to.be("test"); 219 | expect(rewiredModule.__get__("someGlobalVar")).to.be("other value"); 220 | delete global.someGlobalVar; 221 | } else { 222 | window.someGlobalVar = "test"; 223 | rewiredModule = rewire("./moduleA.js"); 224 | rewiredModule.__set__("someGlobalVar", "other value"); 225 | expect(window.someGlobalVar).to.be("test"); 226 | expect(rewiredModule.__get__("someGlobalVar")).to.be("other value"); 227 | if (typeof navigator !== "undefined" && /MSIE [6-8]\.[0-9]/g.test(navigator.userAgent) === false) { 228 | delete window.someGlobalVar; 229 | } 230 | } 231 | }); 232 | 233 | it("should not be a problem to have a comment on file end", function () { 234 | var rewired = rewire("./emptyModule.js"); 235 | 236 | rewired.__set__("someVar", "hello"); 237 | expect(rewired.__get__("someVar")).to.be("hello"); 238 | }); 239 | 240 | it("should not be a problem to have a module that exports a boolean", function( ) { 241 | rewire("./boolean.js"); // should not throw 242 | }); 243 | 244 | it("should not be a problem to have a module that exports null", function () { 245 | rewire("./null.js"); // should not throw 246 | }); 247 | 248 | it("should not be a problem to have a module that exports a sealed object", function () { 249 | rewire("./sealedObject.js"); // should not throw 250 | }); 251 | 252 | (supportsObjectSpread ? it : it.skip)("should not be a problem to have a module that uses object spread operator", function () { 253 | rewire("./objectSpreadOperator.js"); // should not throw 254 | }); 255 | 256 | (supportsObjectRest ? it : it.skip)("should not be a problem to have a module that uses object rest operator", function () { 257 | rewire("./objectRestOperator.js"); // should not throw 258 | }); 259 | 260 | it("should not influence the original require if nothing has been required within the rewired module", function () { 261 | rewire("./emptyModule.js"); // nothing happens here because emptyModule doesn't require anything 262 | expect(require("./moduleA.js").__set__).to.be(undefined); // if restoring the original node require didn't worked, the module would have a setter 263 | }); 264 | 265 | it("subsequent calls of rewire should always return a new instance", function () { 266 | expect(rewire("./moduleA.js")).not.to.be(rewire("./moduleA.js")); 267 | }); 268 | 269 | it("should preserve the strict mode", function () { 270 | var strictModule = rewire("./strictModule.js"); 271 | 272 | expect(function () { 273 | strictModule.doSomethingUnstrict(); 274 | }).to.throwException(checkForTypeError); 275 | }); 276 | 277 | it("should not modify line numbers in stack traces", function () { 278 | var throwError = rewire("./throwError.js"); 279 | 280 | try { 281 | throwError(); 282 | } catch (err) { 283 | 284 | // Firefox implements a different error-stack format, 285 | // but does offer line and column numbers on errors: we use 286 | // those instead. 287 | if (err.lineNumber !== undefined && err.columnNumber !== undefined) { 288 | expect(err.lineNumber).to.equal(6) 289 | expect(err.columnNumber).to.equal(26) 290 | } 291 | // This is for the V8 stack trace format (Node, Chrome) 292 | else { 293 | expect(err.stack.split("\n")[1]).to.match(/:6:26/); 294 | } 295 | } 296 | }); 297 | 298 | it("should be possible to set implicit globals", function () { 299 | var implicitGlobalModule, 300 | err; 301 | 302 | try { 303 | implicitGlobalModule = rewire("./implicitGlobal.js"); 304 | 305 | implicitGlobalModule.__set__("implicitGlobal", true); 306 | expect(implicitGlobalModule.__get__("implicitGlobal")).to.be(true); 307 | // setting implicit global vars will change them globally instead of locally. 308 | // that's a shortcoming of the current implementation which can't be solved easily. 309 | //expect(implicitGlobal).to.be.a("string"); 310 | } catch (e) { 311 | err = e; 312 | } finally { 313 | // Cleaning up... 314 | delete global.implicitGlobal; 315 | delete global.undefinedImplicitGlobal; 316 | } 317 | 318 | if (err) { 319 | throw err; 320 | } 321 | }); 322 | 323 | it("should throw a TypeError if the path is not a string", function () { 324 | expect(function () { 325 | rewire(null); 326 | }).to.throwException(checkForTypeError); 327 | }); 328 | 329 | it("should also revert nested changes (with dot notation)", function () { 330 | var rewiredModuleA = rewire("./moduleA.js"), 331 | revert; 332 | 333 | revert = rewiredModuleA.__set__("myObj.test", true); 334 | expect(rewiredModuleA.getMyObj()).to.eql({ 335 | test: true 336 | }); 337 | revert(); 338 | // This test also demonstrates a known drawback of the current implementation 339 | // If the value doesn't exist at the time it is about to be set, it will be 340 | // reverted to undefined instead deleting it from the object 341 | // However, this is probably not a real world use-case because why would you 342 | // want to mock something when it is not set. 343 | expect(rewiredModuleA.getMyObj()).to.eql({ 344 | test: undefined 345 | }); 346 | 347 | revert = rewiredModuleA.__set__({ 348 | "myObj.test": true 349 | }); 350 | expect(rewiredModuleA.getMyObj()).to.eql({ 351 | test: true 352 | }); 353 | revert(); 354 | expect(rewiredModuleA.getMyObj()).to.eql({ 355 | test: undefined 356 | }); 357 | 358 | }); 359 | 360 | it("should be possible to mock undefined, implicit globals", function () { 361 | var implicitGlobalModule, 362 | err; 363 | 364 | try { 365 | implicitGlobalModule = rewire("./implicitGlobal.js"); 366 | implicitGlobalModule.__set__("undefinedImplicitGlobal", "yoo!"); 367 | expect(implicitGlobalModule.__get__("undefinedImplicitGlobal")).to.equal("yoo!"); 368 | 369 | implicitGlobalModule = rewire("./implicitGlobal.js"); 370 | implicitGlobalModule.__set__({ 371 | undefinedImplicitGlobal: "bro!" 372 | }); 373 | expect(implicitGlobalModule.__get__("undefinedImplicitGlobal")).to.equal("bro!"); 374 | } catch (e) { 375 | err = e; 376 | } finally { 377 | // Cleaning up... 378 | delete global.implicitGlobal; 379 | delete global.undefinedImplicitGlobal; 380 | } 381 | 382 | if (err) { 383 | throw err; 384 | } 385 | }); 386 | 387 | it("should be possible to mock and revert JSON.parse (see #40)", function () { 388 | var moduleA = rewire("./moduleA.js"), 389 | revert; 390 | 391 | revert = moduleA.__set__({ 392 | JSON: { 393 | parse: function () { return true; } 394 | } 395 | }); 396 | 397 | revert(); 398 | }); 399 | 400 | it("should be possible to set a const variable", function () { 401 | var constModule = rewire("./constModule"); 402 | var varNames = Object.keys(constModule); 403 | 404 | expect(varNames.length).to.be.greaterThan(0); 405 | 406 | varNames.forEach(varName => { 407 | constModule.__set__(varName, "this has been changed"); // should not throw 408 | expect(constModule[varName]()).to.equal("this has been changed"); 409 | }); 410 | }); 411 | 412 | it("should fail with a helpful TypeError when const is re-assigned", function () { 413 | expect(function () { 414 | rewire("./wrongConstModule"); 415 | }).to.throwException(/^Assignment to constant variable at .+?wrongConstModule\.js:4:1$/); 416 | }); 417 | 418 | it("should be possible to rewire shebang modules", function () { 419 | var shebangModule = rewire("./shebangModule"); 420 | var shebangs = shebangModule.__get__("shebangs"); 421 | 422 | expect(typeof shebangs).to.be("function"); 423 | expect(shebangModule.shebangs()).to.be(true); 424 | }); 425 | 426 | it("should be possible to re-assign consts", function () { 427 | var test = rewire("./constModule.js"); 428 | 429 | test.__set__("j", "some other value"); 430 | 431 | expect(test.j()).to.be("some other value"); 432 | }); 433 | }; 434 | -------------------------------------------------------------------------------- /testLib/shebangModule.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | function shebangs() { 4 | return true; 5 | } 6 | 7 | exports.shebangs = shebangs; 8 | -------------------------------------------------------------------------------- /testLib/someOtherModule.js: -------------------------------------------------------------------------------- 1 | "use strict"; // run code in ES5 strict mode 2 | 3 | __filename = "/test/testModules/someOtherModule.js"; 4 | 5 | exports.fs = {}; 6 | exports.filename = __filename; 7 | exports.name = "somOtherModule"; 8 | -------------------------------------------------------------------------------- /testLib/strictModule.js: -------------------------------------------------------------------------------- 1 | "use strict"; // run code in ES5 strict mode 2 | 3 | function doSomethingUnstrict() { 4 | var caller = arguments.callee.caller; // this should throw an error as a proof that strict mode is on 5 | } 6 | 7 | exports.doSomethingUnstrict = doSomethingUnstrict; -------------------------------------------------------------------------------- /testLib/throwError.js: -------------------------------------------------------------------------------- 1 | // Using deliberately const here because we know that we're transform const to let 2 | const a = "a"; 3 | 4 | module.exports = function () { 5 | // Ensure that column numbers are correct 6 | const b = "b"; throw new Error(); 7 | }; 8 | -------------------------------------------------------------------------------- /testLib/wrongConstModule.js: -------------------------------------------------------------------------------- 1 | // Assigning to a const should fail 2 | const a = "a"; 3 | 4 | a = "b"; 5 | --------------------------------------------------------------------------------