proxyquire
Advanced tools
Comparing version 0.6.0 to 1.0.0
'use strict'; | ||
var Proxyquire = require('./lib/proxyquire') | ||
, compat = require('./lib/compat'); | ||
var Proxyquire = require('./lib/proxyquire'); | ||
@@ -10,2 +9,4 @@ // delete this module from the cache to force re-require in order to allow resolving test module via parent.module | ||
module.exports = new Proxyquire(module.parent); | ||
module.exports.compat = function (useGlobal) { return compat(module.parent, useGlobal); }; | ||
module.exports.compat = function() { | ||
throw new Error("Proxyquire compat mode has been removed. Please update your code to use the new API or pin the version in your package.json file to ~0.6"); | ||
}; |
@@ -8,2 +8,3 @@ 'use strict'; | ||
, is = require('./is') | ||
, assert = require('assert') | ||
; | ||
@@ -118,58 +119,129 @@ | ||
// Find the ID (location) of the SUT, relative to the parent | ||
var id = Module._resolveFilename(request, this._parent); | ||
// Find out if any of the passed stubs are global overrides | ||
for (var key in stubs) { | ||
if (stubs[key].hasOwnProperty('@global')) { | ||
this._containsGlobal = true; | ||
} | ||
// Temporarily delete the SUT from the require cache, if it exists. | ||
var cached = Module._cache[id]; | ||
if (cached) delete Module._cache[id]; | ||
if (stubs[key].hasOwnProperty('@runtimeGlobal')) { | ||
this._containsGlobal = true; | ||
this._containsRuntimeGlobal = true; | ||
} | ||
} | ||
// Override the core require function for the SUT's file extension. | ||
var extname = path.extname(id); | ||
var ext_super = require.extensions[extname]; | ||
// Ignore the module cache when return the requested module | ||
return this._withoutCache(this._parent, stubs, request, this._parent.require.bind(this._parent, request)); | ||
}; | ||
var self = this; | ||
require.extensions[extname] = function ext(module, filename) { | ||
// NOTE: This function is for requiring the SUT | ||
// This replaces a module's require function | ||
Proxyquire.prototype._require = function(module, stubs, path) { | ||
assert(typeof path === 'string', 'path must be a string'); | ||
assert(path, 'missing path'); | ||
// require_super is the normal require for the SUT. | ||
var require_super = module.require.bind(module); | ||
require_super.extensions = require.extensions; | ||
require_super.extensions[extname] = ext_super; | ||
require_super.main = process.mainModule; | ||
if (stubs.hasOwnProperty(path)) { | ||
var stub = stubs[path]; | ||
module.require = function (request) { | ||
// NOTE: This function is for requiring dependencies for the SUT | ||
if (stub.hasOwnProperty('@noCallThru') ? !stub['@noCallThru'] : !this._noCallThru) { | ||
fillMissingKeys(stub, Module._load(path, module)); | ||
} | ||
// If the request string isn't stubbed, just do the usual thing. | ||
if (!stubs.hasOwnProperty(request)) return require_super(request); | ||
// We are top level or this stub is marked as global | ||
if (module.parent == this._parent || stub.hasOwnProperty('@global') || stub.hasOwnProperty('@runtimeGlobal')) { | ||
return stub; | ||
} | ||
} | ||
var stub = stubs[request]; | ||
// Only ignore the cache if we have global stubs | ||
if (this._containsRuntimeGlobal) { | ||
return this._withoutCache(module, stubs, path, Module._load.bind(Module, path, module)); | ||
} else { | ||
return Module._load(path, module); | ||
} | ||
}; | ||
if (stub.hasOwnProperty('@noCallThru') ? !stub['@noCallThru'] : !self._noCallThru) | ||
fillMissingKeys(stub, require_super(request)); | ||
Proxyquire.prototype._withoutCache = function(module, stubs, path, func) { | ||
// Temporarily disable the cache - either per-module or globally if we have global stubs | ||
var restoreCache = this._disableCache(module, path); | ||
return stub; | ||
}; | ||
// Override all require extension handlers | ||
var restoreExtensionHandlers = this._overrideExtensionHandlers(module, stubs); | ||
// Now that we've overridden the SUT's require, we can proceed as usual. | ||
return ext_super(module, filename); | ||
}; | ||
try { | ||
return this._parent.require(request); | ||
// Execute the function that needs the module cache disabled | ||
return func(); | ||
} finally { | ||
if (self._preserveCache && cached) { | ||
if (cached) | ||
Module._cache[id] = cached; | ||
else | ||
delete Module._cache[id]; | ||
// Restore the cache if we are preserving it | ||
if (this._preserveCache) { | ||
restoreCache(); | ||
} | ||
if (ext_super) | ||
require.extensions[extname] = ext_super; | ||
} | ||
else | ||
delete require.cache[id]; | ||
// Finally restore the original extension handlers | ||
restoreExtensionHandlers(); | ||
} | ||
}; | ||
Proxyquire.prototype._disableCache = function(module, path) { | ||
if (this._containsGlobal) { | ||
// empty the require cache because if we are stubbing C but requiring A, | ||
// and if A requires B and B requires C, then B and C might be cached already | ||
// and we'll never get the chance to return our stub | ||
return this._disableGlobalCache(); | ||
} | ||
// Temporarily delete the SUT from the require cache | ||
return this._disableModuleCache(path, module); | ||
}; | ||
Proxyquire.prototype._disableGlobalCache = function() { | ||
var cache = require.cache; | ||
require.cache = Module._cache = {}; | ||
// Return a function that will undo what we just did | ||
return function() { | ||
require.cache = Module._cache = cache; | ||
}; | ||
}; | ||
Proxyquire.prototype._disableModuleCache = function(path, module) { | ||
// Find the ID (location) of the SUT, relative to the parent | ||
var id = Module._resolveFilename(path, module); | ||
var cached = Module._cache[id]; | ||
delete Module._cache[id]; | ||
// Return a function that will undo what we just did | ||
return function() { | ||
if (cached) { | ||
Module._cache[id] = cached; | ||
} | ||
}; | ||
}; | ||
Proxyquire.prototype._overrideExtensionHandlers = function(module, stubs) { | ||
var originalExtensions = {}; | ||
var self = this; | ||
Object.keys(require.extensions).forEach(function(extension) { | ||
// Store the original so we can restore it later | ||
if (!originalExtensions[extension]) { | ||
originalExtensions[extension] = require.extensions[extension]; | ||
} | ||
// Override the default handler for the requested file extension | ||
require.extensions[extension] = function(module, filename) { | ||
// Override the require method for this module | ||
module.require = self._require.bind(self, module, stubs); | ||
return originalExtensions[extension](module, filename); | ||
}; | ||
}); | ||
// Return a function that will undo what we just did | ||
return function() { | ||
Object.keys(originalExtensions).forEach(function(extension) { | ||
require.extensions[extension] = originalExtensions[extension]; | ||
}); | ||
}; | ||
}; | ||
module.exports = Proxyquire; |
{ | ||
"name": "proxyquire", | ||
"version": "0.6.0", | ||
"version": "1.0.0", | ||
"description": "Proxies nodejs require in order to allow overriding dependencies during testing.", | ||
@@ -32,6 +32,6 @@ "main": "index.js", | ||
"devDependencies": { | ||
"mocha": "1.3.x", | ||
"should": "0.6.x", | ||
"sinon": "~1.4.2" | ||
"mocha": "~1.18", | ||
"should": "~3.3", | ||
"sinon": "~1.9" | ||
} | ||
} |
198
README.md
@@ -13,3 +13,3 @@ # proxyquire [![Build Status](https://secure.travis-ci.org/thlorenz/proxyquire.png)](http://travis-ci.org/thlorenz/proxyquire) | ||
- **no changes to your code** are necessary | ||
- **no changes to your code** are necessary | ||
- non overriden methods of a module behave like the original | ||
@@ -26,7 +26,7 @@ - mocking framework agnostic, if it can stub a function then it works with proxyquire | ||
module.exports.extnameAllCaps = function (file) { | ||
module.exports.extnameAllCaps = function (file) { | ||
return path.extname(file).toUpperCase(); | ||
}; | ||
module.exports.basenameAllCaps = function (file) { | ||
module.exports.basenameAllCaps = function (file) { | ||
return path.basename(file).toUpperCase(); | ||
@@ -57,2 +57,4 @@ }; | ||
<!-- START doctoc generated TOC please keep comment here to allow auto update --> | ||
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE --> | ||
**Table of Contents** *generated with [DocToc](http://doctoc.herokuapp.com/)* | ||
@@ -62,11 +64,19 @@ | ||
- [API](#api) | ||
- [Preventing call thru to original dependency](#preventing-call-thru-to-original-dependency) | ||
- [Prevent call thru for all future stubs resolved by a proxyquire instance](#prevent-call-thru-for-all-future-stubs-resolved-by-a-proxyquire-instance) | ||
- [Re-enable call thru for all future stubs resolved by a proxyquire instance](#re-enable-call-thru-for-all-future-stubs-resolved-by-a-proxyquire-instance) | ||
- [All together, now](#all-together-now) | ||
- [Forcing proxyquire to reload modules](#forcing-proxyquire-to-reload-modules) | ||
- [Examples](#examples) | ||
- [Preventing call thru to original dependency](#preventing-call-thru-to-original-dependency) | ||
- [Prevent call thru for all future stubs resolved by a proxyquire instance](#prevent-call-thru-for-all-future-stubs-resolved-by-a-proxyquire-instance) | ||
- [Re-enable call thru for all future stubs resolved by a proxyquire instance](#re-enable-call-thru-for-all-future-stubs-resolved-by-a-proxyquire-instance) | ||
- [All together, now](#all-together-now) | ||
- [Forcing proxyquire to reload modules](#forcing-proxyquire-to-reload-modules) | ||
- [Globally override require](#globally-override-require) | ||
- [Caveat](#caveat) | ||
- [Globally override require during module initialization](#globally-override-require-during-module-initialization) | ||
- [Why is proxyquire messing with my `require` cache?](#why-is-proxyquire-messing-with-my-require-cache) | ||
- [Globally override require during module runtime](#globally-override-require-during-module-runtime) | ||
- [Backwards Compatibility for proxyquire v0.3.x](#backwards-compatibility-for-proxyquire-v03x) | ||
- [Examples](#examples) | ||
- [More Examples](#more-examples) | ||
<!-- END doctoc generated TOC please keep comment here to allow auto update --> | ||
# Usage | ||
@@ -85,3 +95,3 @@ | ||
- **stubs**: key/value pairs of the form `{ modulePath: stub, ... }` | ||
- module paths are relative to the tested module **not** the test file | ||
- module paths are relative to the tested module **not** the test file | ||
- therefore specify it exactly as in the require statement inside the tested file | ||
@@ -138,4 +148,4 @@ - values themselves are key/value pairs of functions/properties and the appropriate override | ||
// for 'path' module they will be made | ||
, path: { | ||
extname: function (file) { ... } | ||
, path: { | ||
extname: function (file) { ... } | ||
, '@noCallThru': false | ||
@@ -146,3 +156,3 @@ } | ||
### All together, now | ||
#### All together, now | ||
@@ -161,6 +171,6 @@ ```javascript | ||
### Forcing proxyquire to reload modules | ||
## Forcing proxyquire to reload modules | ||
In most situations it is fine to have proxyquire behave exactly like nodejs `require`, i.e. modules that are loaded once | ||
get pulled from the cache the next time. | ||
get pulled from the cache the next time. | ||
@@ -199,4 +209,140 @@ For some tests however you need to ensure that the module gets loaded fresh everytime, i.e. if that causes initializing | ||
## Examples | ||
## Globally override require | ||
Use the `@global` property to override every `require` of a module, even transitively. | ||
### Caveat | ||
You should **think very hard about alternatives before using this feature**. Why, because it's intrusive and as you'll | ||
see if you read on it changes the default behavior of module initialization which means that code runs differently | ||
during testing than it does normally. | ||
Additionally it **makes it harder to reason about how your tests work**. | ||
> Yeah, we are mocking `fs` three levels down in `bar`, so that's why we have to set it up when testing `foo` | ||
**WAAAT???** | ||
If you write proper unit tests you should never have a need for this. So here are some techniques to consider: | ||
- test each module in isolation | ||
- make sure your modules are small enough and do only one thing | ||
- stub out dependencies direclty instead of stubbing something inside your dependencies | ||
- if you are testing `bar` and `bar` calls `foo.read` and `foo.read` calls `fs.readFile` proceed as follows | ||
- **do not** stub out `fs.readFile` globally | ||
- instead stub out `foo` so you can control what `foo.read` returns without ever even hitting `fs` | ||
OK, made it past the warnings and still feel like you need this? Read on then but you are on your own now, this is as | ||
far as I'll go ;) | ||
Watch out for more warnings below. | ||
### Globally override require during module initialization | ||
```javascript | ||
// foo.js | ||
var bar = require('bar'); | ||
module.exports = function() { | ||
bar(); | ||
} | ||
// bar.js | ||
var baz = require('baz'); | ||
module.exports = function() { | ||
baz.method(); | ||
} | ||
// baz.js | ||
module.exports = { | ||
method: function() { | ||
console.info('hello'); | ||
} | ||
} | ||
// test.js | ||
var stubs = { | ||
'baz': { | ||
method: function(val) { | ||
console.info('goodbye'); | ||
}, | ||
'@global': true | ||
} | ||
}; | ||
var proxyquire = require('proxyquire'); | ||
var foo = proxyquire('foo', stubs); | ||
foo(); // 'goodbye' is printed to stdout | ||
``` | ||
Be aware that when using global overrides **any module initialization code will be re-executed for each require.** | ||
This is not normally the case since node.js caches the return value of `require`, however to make global overrides work , | ||
`proxyquire` bypasses the module cache. This may cause **unexpected behaviour if a module's initialization causes side effects**. | ||
As an example consider this module which opens a file during its initialization: | ||
```javascript | ||
var fs = require('fs') | ||
, C = require('C'); | ||
// will get executed twice | ||
var file = fs.openSync('/tmp/foo.txt', 'w'); | ||
module.exports = function() { | ||
return new C(file); | ||
}; | ||
``` | ||
The file at `/tmp/foo.txt` could be created and/or truncated more than once. | ||
### Why is proxyquire messing with my `require` cache? | ||
Say you have a module, C, that you wish to stub. You require module A which contains `require('B')`. Module B in turn | ||
contains `require('C')`. If module B has already been required elsewhere then when module A receives the cached version | ||
of module B and proxyquire would have no opportunity to inject the stub for C. | ||
Therefore when using the `@global` flag, `proxyquire` will bypass the `require` cache. | ||
### Globally override require during module runtime | ||
Say you have a module that looks like this: | ||
```javascript | ||
module.exports = function() { | ||
var d = require('d'); | ||
d.method(); | ||
}; | ||
``` | ||
The invocation of `require('d')` will happen at runtime and not when the containing module is requested via `require`. | ||
If you want to globally override `d` above, use the `@runtimeGlobal` property: | ||
```javascript | ||
var stubs = { | ||
'd': { | ||
method: function(val) { | ||
console.info('hello world'); | ||
}, | ||
'@runtimeGlobal': true | ||
} | ||
}; | ||
``` | ||
This will cause module setup code to be re-excuted just like `@global`, but with the difference that it will happen | ||
every time the module is requested via `require` at runtime as no module will ever be cached. | ||
This can cause subtle bugs so if you can guarantee that your modules will not vary their `require` behaviour at runtime, | ||
use `@global` instead. | ||
# Backwards Compatibility for proxyquire v0.3.x | ||
Compatibility mode with proxyquire v0.3.x **has been removed**. | ||
You should update your code to use the newer API but if you can't, pin the version of proxyquire in your package.json file to ~0.6 in order to continue using the older style. | ||
# Examples | ||
**We are testing foo which depends on bar:** | ||
@@ -250,7 +396,7 @@ | ||
var foo = proxyquire('./foo', { | ||
'./bar' : { | ||
toAtm: function (val) { return 0; /* wonder what happens now */ } | ||
'./bar' : { | ||
toAtm: function (val) { return 0; /* wonder what happens now */ } | ||
} | ||
, path : { | ||
extname: function (file) { return 'exterminate the name of ' + file; } | ||
, path : { | ||
extname: function (file) { return 'exterminate the name of ' + file; } | ||
} | ||
@@ -260,13 +406,2 @@ }); | ||
# Backwards Compatibility for proxyquire v0.3.x | ||
To upgrade your project from v0.3.x to v0.4.x, a nifty compat function has been included. | ||
Simply do a global find and replace for `require('proxyquire')` and change them to `require('proxyquire').compat()`. | ||
This returns an object that wraps the result of `proxyquire()` that provides exactly the same API as v0.3.x. | ||
If your test scripts relied on the fact that v0.3.x stored `noCallThru` in the module scope, you can use | ||
`require('proxyquire').compat(true)` to use a global compat object, instead. | ||
# More Examples | ||
@@ -281,2 +416,1 @@ | ||
- using proxyquire with [Sinon.JS](http://sinonjs.org/): [examples/sinon](https://github.com/thlorenz/proxyquire/tree/master/examples/sinon). | ||
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
1
405
53072
46
1123