diff --git a/README.md b/README.md index 15742af..fa3829d 100644 --- a/README.md +++ b/README.md @@ -97,6 +97,7 @@ fetch(function (err, res) { - [All together, now](#all-together-now) - [Using proxyquire to simulate the absence of Modules](#using-proxyquire-to-simulate-the-absence-of-modules) - [Forcing proxyquire to reload modules](#forcing-proxyquire-to-reload-modules) + - [Require all modules to be registered](#require-all-modules-to-be-registered) - [Globally override require](#globally-override-require) - [Caveat](#caveat) - [Globally override require during module initialization](#globally-override-require-during-module-initialization) @@ -262,6 +263,29 @@ assert.equal(foo1, foo2); assert.equal(foo1, foo3); ``` +## Require all modules to be registered + +Proxyquire also gives you the ability to require that all modules have a registered stub. This is useful to ensure that all module dependencies are declared in your tests and can avoid pitfalls such as including modules that: + 1. run in the background + 2. contact network dependencies + 3. contain business logic + +While it makes sense not to mock certain modules you still may want these same modules to be declared and registered as a dependency in your test which is an explicit way to indicate that they are ok to use in your test without being mocked. + +For the purpose of requiring modules to have registered stubs, proxyquire exposes the `requireStubs` function. + +```js +// ensure modules have registered stubs, otherwise cause proxyquire to fail to load +var proxyquire = require('proxyquire').requireStubs().noCallThru(); + +// loads fine +var foo1 = proxyquire('./foo', { path: require('path') }); + +// throws an error since module "path" has no registered stub +var foo2 = proxyquire('./foo', {}); +``` + +`requireStubs` is often used in conjunction with `noCallThru` but the two can be used separately. Modules loaded to support call through behavior can load their dependent modules without stubs being registered. ## Globally override require diff --git a/lib/proxyquire.js b/lib/proxyquire.js index a169c38..c565d35 100644 --- a/lib/proxyquire.js +++ b/lib/proxyquire.js @@ -32,6 +32,8 @@ function Proxyquire (parent) { this._parent = parent this._preserveCache = true + this._requireStubs = false + this._moduleRequiringStubs = null Object.keys(proto) .forEach(function (key) { @@ -96,6 +98,19 @@ Proxyquire.prototype.preserveCache = function () { return this.fn } +/** + * Requires all modules to have a stub registered with proxyquire + * + * @name requireStubs + * @function + * @private + * @return {object} The proxyquire function to allow chaining + */ +Proxyquire.prototype.requireStubs = function () { + this._requireStubs = true + return this.fn +} + /** * Loads a module using the given stubs instead of their normally resolved required modules. * @param request The requirable module path to load. @@ -105,6 +120,13 @@ Proxyquire.prototype.preserveCache = function () { Proxyquire.prototype.load = function (request, stubs) { validateArguments(request, stubs) + if (this._requireStubs) { + var modulePath = Module._resolveFilename(request, this._parent) + this._moduleRequiringStubs = modulePath + } else { + this._moduleRequiringStubs = null + } + // Find out if any of the passed stubs are global overrides for (var key in stubs) { var stub = stubs[key] @@ -186,6 +208,10 @@ Proxyquire.prototype._require = function (module, stubs, path) { } } + if (this._moduleRequiringStubs === module.filename) { + throw new ProxyquireError('Module at path "' + path + '" does not have a registered stub with proxyquire') + } + // Only ignore the cache if we have global stubs if (this._containsRuntimeGlobal) { return this._withoutCache(module, stubs, path, Module._load.bind(Module, path, module)) diff --git a/package.json b/package.json index 8fec26b..5492424 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "proxyquire", - "version": "2.1.0", + "version": "2.2.0", "description": "Proxies nodejs require in order to allow overriding dependencies during testing.", "main": "index.js", "scripts": { diff --git a/test/proxyquire-cache.js b/test/proxyquire-cache.js index a53eb68..3f9e55f 100644 --- a/test/proxyquire-cache.js +++ b/test/proxyquire-cache.js @@ -1,6 +1,7 @@ 'use strict' var assert = require('assert') +var ProxyError = require('../lib/proxyquire-error') describe('Proxyquire', function () { describe('load()', function () { @@ -115,4 +116,50 @@ describe('Proxyquire', function () { }) }) }) + + describe('requireStubs()', function () { + it('returns a reference to itself, so it can be chained', function () { + var proxyquire = require('..') + assert.equal(proxyquire.requireStubs(), proxyquire) + }) + + it('throws an error when a require statement without a registered stub is loaded', () => { + var proxyquire = require('..') + proxyquire.requireStubs() + + assert.throws(function () { + proxyquire.load('./samples/require-stubs/dep2', {}) + }, function (err) { + return (err instanceof ProxyError) && + err.message === 'Module at path "./dep3" does not have a registered stub with proxyquire' + }) + + assert.doesNotThrow(function () { + proxyquire.load('./samples/require-stubs/dep2', { './dep3': {} }) + }, 'Unexpected error when loading a module that has a registered stub') + }) + + it('allows call through modules to load their dependencies without registered stubs', function () { + var proxyquire = require('..') + proxyquire.requireStubs() + + // Default call through behavior still works + try { + var dep1 = proxyquire.load('./samples/require-stubs/dep1', { + './dep2': {} + }) + + // Dependency Chain: + // - dependency 1 requires depencency 2 + // - dependency 2 requires dependency 3 + // + // Notes: + // because call through behavior is allowed dependency 2 is loaded which then loads dependency 3 + // even though no stub was registered for dependency 3 ('./dep3') + assert.equal(dep1.dep2.dep3.name, 'dep3') + } catch (err) { + assert.fail(err) + } + }) + }) }) diff --git a/test/samples/require-stubs/dep1.js b/test/samples/require-stubs/dep1.js new file mode 100644 index 0000000..2f43d45 --- /dev/null +++ b/test/samples/require-stubs/dep1.js @@ -0,0 +1,4 @@ +module.exports = { + name: 'dep1', + dep2: require('./dep2') +} diff --git a/test/samples/require-stubs/dep2.js b/test/samples/require-stubs/dep2.js new file mode 100644 index 0000000..0d98387 --- /dev/null +++ b/test/samples/require-stubs/dep2.js @@ -0,0 +1,4 @@ +module.exports = { + name: 'dep2', + dep3: require('./dep3') +} diff --git a/test/samples/require-stubs/dep3.js b/test/samples/require-stubs/dep3.js new file mode 100644 index 0000000..3ef1d00 --- /dev/null +++ b/test/samples/require-stubs/dep3.js @@ -0,0 +1,3 @@ +module.exports = { + name: 'dep3' +}