rewire

Easy monkey-patching for node.js unit tests

README

rewire
======
Easy monkey-patching for node.js unit tests
undefined undefined Dependency Status Build Status Coverage Status

rewire adds a special setter and getter to modules so you can modify their behaviour for better unit testing. You may

- inject mocks for other modules or globals like process
- inspect private variables
- override variables within the module.

Please note: The current version of rewire is only compatible with CommonJS modules. See Limitations.

Installation

npm install rewire

Introduction

Imagine you want to test this module:

  1. ``` js
  2. // lib/myModule.js
  3. // With rewire you can change all these variables
  4. var fs = require("fs"),
  5.     path = "/somewhere/on/the/disk";

  6. function readSomethingFromFileSystem(cb) {
  7.     console.log("Reading from file system ...");
  8.     fs.readFile(path, "utf8", cb);
  9. }

  10. exports.readSomethingFromFileSystem = readSomethingFromFileSystem;
  11. ```

Now within your test module:

  1. ``` js
  2. // test/myModule.test.js
  3. var rewire = require("rewire");

  4. var myModule = rewire("../lib/myModule.js");
  5. ```

rewire acts exactly like require. With just one difference: Your module will now export a special setter and getter for private variables.

  1. ``` js
  2. myModule.__set__("path", "/dev/null");
  3. myModule.__get__("path"); // = '/dev/null'
  4. ```

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.

  1. ``` js
  2. var fsMock = {
  3.     readFile: function (path, encoding, cb) {
  4.         expect(path).to.equal("/somewhere/on/the/disk");
  5.         cb(null, "Success!");
  6.     }
  7. };
  8. myModule.__set__("fs", fsMock);

  9. myModule.readSomethingFromFileSystem(function (err, data) {
  10.     console.log(data); // = Success!
  11. });
  12. ```

You can also set multiple variables with one call.

  1. ``` js
  2. myModule.__set__({
  3.     fs: fsMock,
  4.     path: "/dev/null"
  5. });
  6. ```

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.

  1. ``` js
  2. myModule.__set__({
  3.     console: {
  4.         log: function () { /* be quiet */ }
  5.     },
  6.     process: {
  7.         argv: ["testArg1", "testArg2"]
  8.     }
  9. });
  10. ```

__set__ returns a function which reverts the changes introduced by this particular __set__ call

  1. ``` js
  2. var revert = myModule.__set__("port", 3000);

  3. // port is now 3000
  4. revert();
  5. // port is now the previous value
  6. ```

For your convenience you can also use the __with__ method which reverts the given changes after it finished.

  1. ``` js
  2. myModule.__with__({
  3.     port: 3000
  4. })(function () {
  5.     // within this function port is 3000
  6. });
  7. // now port is the previous value again
  8. ```

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.

  1. ``` js
  2. myModule.__with__({
  3.     port: 3000
  4. })(function () {
  5.     return new Promise(...);
  6. }).then(function () {
  7.     // now port is the previous value again
  8. });
  9. // port is still 3000 here because the promise hasn't been resolved yet
  10. ```

Limitations

**Babel's ES module emulation**
During the transpilation step from ESM to CJS modules, Babel renames internal variables. Rewire will not work in these cases (see #62). Other Babel transforms, however, should be fine. Another solution might be switching to babel-plugin-rewire.

**Variables inside functions**
Variables inside functions can not be changed by rewire. This is constrained by the language.

  1. ``` js
  2. // myModule.js
  3. (function () {
  4.     // Can't be changed by rewire
  5.     var someVariable;
  6. })()
  7. ```

**Modules that export primitives**
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.

  1. ``` js
  2. // Will throw an error if it's loaded with rewire()
  3. module.exports = 2;
  4. ```

**Globals with invalid variable names**
rewire imports global variables into the local scope by prepending a list of var declarations:

  1. ``` js
  2. var someGlobalVar = global.someGlobalVar;
  3. ```

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.

**Special globals**
Please be aware that you can't rewire eval() or the global object itself.

API

rewire(filename: String): rewiredModule


Returns a rewired version of the module found at filename. Use rewire() exactly like require().

rewiredModule.__set__(name: String, value: *): Function


Sets the internal variable name to the given value. Returns a function which can be called to revert the change.

rewiredModule.__set__(obj: Object): Function


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.

rewiredModule.__get__(name: String): *


Returns the private variable with the given name.

rewiredModule.__with__(obj: Object): Function<callback: Function>


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.

Caveats

**Difference to require()**
Every call of rewire() executes the module again and returns a fresh instance.

  1. ``` js
  2. rewire("./myModule.js") === rewire("./myModule.js"); // = false
  3. ```

This can especially be a problem if the module is not idempotent like mongoose models.

**Globals are imported into the module's scope at the time of rewiring**
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).

**Dot notation**
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:

  1. ``` js
  2. myModule.__set__("console", {
  3.     log: function () {}
  4. });
  5. ```

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.

webpack

CoffeeScript

Good news to all caffeine-addicts: rewire works also with Coffee-Script. Note that in this case you need to install thecoffeescript package.

License


MIT