├── .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://www.npmjs.com/package/rewire)
6 | [](https://www.npmjs.com/package/rewire)
7 | [](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 |
--------------------------------------------------------------------------------