From 59df340d9cf519b7ff99fd74aa521e377b58ea21 Mon Sep 17 00:00:00 2001 From: Samuel Smith Date: Sat, 8 Dec 2018 09:35:35 -0700 Subject: [PATCH 1/3] Added requireStubs() feature - Updated README with instructions - Bumped version to 2.2.0 --- README.md | 23 +++++++++++++++++++++++ lib/proxyquire.js | 20 ++++++++++++++++++++ package.json | 2 +- test/proxyquire-cache.js | 24 ++++++++++++++++++++++++ test/samples/require-bar.js | 1 + 5 files changed, 69 insertions(+), 1 deletion(-) create mode 100644 test/samples/require-bar.js diff --git a/README.md b/README.md index 15742af..91c2b1f 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,28 @@ 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(); + +// loads fine +var foo1 = proxyquire('./foo', { path: require('path') }); + +// throws an error since module "path" has no registered stub +var foo2 = proxyquire('./foo', {}); +``` + ## Globally override require diff --git a/lib/proxyquire.js b/lib/proxyquire.js index a169c38..509d151 100644 --- a/lib/proxyquire.js +++ b/lib/proxyquire.js @@ -32,6 +32,7 @@ function Proxyquire (parent) { this._parent = parent this._preserveCache = true + this._requireStubs = false Object.keys(proto) .forEach(function (key) { @@ -96,6 +97,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. @@ -186,6 +200,12 @@ Proxyquire.prototype._require = function (module, stubs, path) { } } + if (this._requireStubs) { + var requireStubsError = new Error('Module at path "' + path + '" does not have a registered stub with proxyquire') + requireStubsError.code = 'MODULE_NOT_REGISTERED' + throw requireStubsError + } + // 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..50e9eff 100644 --- a/test/proxyquire-cache.js +++ b/test/proxyquire-cache.js @@ -115,4 +115,28 @@ 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-bar', {}) + }, function (err) { + return (err instanceof Error) && + err.message === 'Module at path "./bar" does not have a registered stub with proxyquire' && + err.code === 'MODULE_NOT_REGISTERED' + }, 'Expected an error to be thrown when encountering a require statement for a module without a registered stub') + + assert.doesNotThrow(function () { + proxyquire.load('./samples/require-bar', { './bar': {} }) + }, 'Unexpected error when loading a require that had a registered stub') + }) + }) }) diff --git a/test/samples/require-bar.js b/test/samples/require-bar.js new file mode 100644 index 0000000..9ed71d9 --- /dev/null +++ b/test/samples/require-bar.js @@ -0,0 +1 @@ +require('./bar') From e844f7ac7b0d9e1e0269e249bfcf23f535c24bfe Mon Sep 17 00:00:00 2001 From: Sam Smith Date: Sat, 8 Dec 2018 09:44:44 -0700 Subject: [PATCH 2/3] Updated test error statement to be more clear --- test/proxyquire-cache.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/proxyquire-cache.js b/test/proxyquire-cache.js index 50e9eff..62ccdf2 100644 --- a/test/proxyquire-cache.js +++ b/test/proxyquire-cache.js @@ -136,7 +136,7 @@ describe('Proxyquire', function () { assert.doesNotThrow(function () { proxyquire.load('./samples/require-bar', { './bar': {} }) - }, 'Unexpected error when loading a require that had a registered stub') + }, 'Unexpected error when loading a module that had a registered stub') }) }) }) From 0e2cc7d764e6d6b803cfdb1c0cd0df77868834b9 Mon Sep 17 00:00:00 2001 From: Samuel Smith Date: Sun, 9 Dec 2018 00:09:23 -0700 Subject: [PATCH 3/3] Added support for call through behavior - Modules loaded to support call through behavior are allowed to load dependent modules without registered stubs - Updated README --- README.md | 3 ++- lib/proxyquire.js | 14 ++++++++---- test/proxyquire-cache.js | 35 +++++++++++++++++++++++++----- test/samples/require-bar.js | 1 - test/samples/require-stubs/dep1.js | 4 ++++ test/samples/require-stubs/dep2.js | 4 ++++ test/samples/require-stubs/dep3.js | 3 +++ 7 files changed, 52 insertions(+), 12 deletions(-) delete mode 100644 test/samples/require-bar.js create mode 100644 test/samples/require-stubs/dep1.js create mode 100644 test/samples/require-stubs/dep2.js create mode 100644 test/samples/require-stubs/dep3.js diff --git a/README.md b/README.md index 91c2b1f..fa3829d 100644 --- a/README.md +++ b/README.md @@ -276,7 +276,7 @@ For the purpose of requiring modules to have registered stubs, proxyquire expose ```js // ensure modules have registered stubs, otherwise cause proxyquire to fail to load -var proxyquire = require('proxyquire').requireStubs(); +var proxyquire = require('proxyquire').requireStubs().noCallThru(); // loads fine var foo1 = proxyquire('./foo', { path: require('path') }); @@ -285,6 +285,7 @@ var foo1 = proxyquire('./foo', { path: require('path') }); 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 509d151..c565d35 100644 --- a/lib/proxyquire.js +++ b/lib/proxyquire.js @@ -33,6 +33,7 @@ function Proxyquire (parent) { this._parent = parent this._preserveCache = true this._requireStubs = false + this._moduleRequiringStubs = null Object.keys(proto) .forEach(function (key) { @@ -119,6 +120,13 @@ Proxyquire.prototype.requireStubs = 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] @@ -200,10 +208,8 @@ Proxyquire.prototype._require = function (module, stubs, path) { } } - if (this._requireStubs) { - var requireStubsError = new Error('Module at path "' + path + '" does not have a registered stub with proxyquire') - requireStubsError.code = 'MODULE_NOT_REGISTERED' - throw requireStubsError + 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 diff --git a/test/proxyquire-cache.js b/test/proxyquire-cache.js index 50e9eff..505a30f 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 () { @@ -127,16 +128,38 @@ describe('Proxyquire', function () { proxyquire.requireStubs() assert.throws(function () { - proxyquire.load('./samples/require-bar', {}) + proxyquire.load('./samples/require-stubs/dep2', {}) }, function (err) { - return (err instanceof Error) && - err.message === 'Module at path "./bar" does not have a registered stub with proxyquire' && - err.code === 'MODULE_NOT_REGISTERED' - }, 'Expected an error to be thrown when encountering a require statement for a module without a registered stub') + 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-bar', { './bar': {} }) + proxyquire.load('./samples/require-stubs/dep2', { './dep3': {} }) }, 'Unexpected error when loading a require that had 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-bar.js b/test/samples/require-bar.js deleted file mode 100644 index 9ed71d9..0000000 --- a/test/samples/require-bar.js +++ /dev/null @@ -1 +0,0 @@ -require('./bar') 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' +}