From b8ffd4a87142ba96ed6c924e9d9b1b516adb9713 Mon Sep 17 00:00:00 2001 From: Vadim Ivanov Date: Wed, 4 Nov 2015 11:18:22 +0100 Subject: [PATCH 1/2] Register allowed modules via regexp --- README.md | 9 +++++ mockery.js | 69 ++++++++++++++++++++++++++++++-- test/fixtures/$.object-assign.js | 3 ++ test/logging-allowable.js | 27 +++++++++++++ test/registered.js | 5 +++ 5 files changed, 109 insertions(+), 4 deletions(-) create mode 100644 test/fixtures/$.object-assign.js diff --git a/README.md b/README.md index b6dc837..5e91196 100644 --- a/README.md +++ b/README.md @@ -153,6 +153,15 @@ and similarly to deregister several modules at once, as you would expect: mockery.deregisterAllowables(['async', 'path', 'util']); +Sometimes it's useful to allow bunch of modules to be allowed without a warnings. +For example, when your code transpiled via babel. In that case you can allow them + + mockery.registerAllowableRegExp(/.+\$\.object-assign/ig); + +and similarly to deregister module, as you would expect: + + mockery.deregisterAllowableRegExp(/.+\$\.object-assign/ig); + ### Unhooking By default, the Node module loader will load a given module only once, caching diff --git a/mockery.js b/mockery.js index 15c3459..57cf647 100644 --- a/mockery.js +++ b/mockery.js @@ -38,6 +38,7 @@ var m = require('module'), registeredMocks = {}, registeredSubstitutes = {}, registeredAllowables = {}, + registeredAllowableRegExps = {}, originalLoader = null, originalCache = null, defaultOptions = { @@ -94,8 +95,10 @@ function hookedLoader(request, parent, isMain) { return subst.module; } - if (registeredAllowables.hasOwnProperty(request)) { - allow = registeredAllowables[request]; + if ((registeredAllowables.hasOwnProperty(request) && ( + allow = registeredAllowables[request]) + ) || (allow = isRequesRegisteredAsRegexp(request)) + ) { if (allow.unhook) { file = m._resolveFilename(request, parent); if (file.indexOf('/') !== -1 && allow.paths.indexOf(file) === -1) { @@ -257,6 +260,42 @@ function registerAllowables(mods, unhook) { }); } +/** + * Register a module as 'allowed' using regular expression as a module name, + * even if a mock or substitute for it has not been registered, mockery will + * not complain when it is loaded via 'require'. + */ +function registerAllowableRegExp(regExp, unhook) { + registeredAllowableRegExps[String(regExp)] = { + unhook: !!unhook, + paths: [], + regExp: regExp + }; +} + +/** + * check if request is 'allowed' + * @param request {String} + */ +function isRequesRegisteredAsRegexp(request) { + + var + regExps = Object.keys(registeredAllowableRegExps), + regExpsLen = regExps.length, + allow; + + if (!regExpsLen) { + return false; + } + while(regExpsLen--) { + allow = registeredAllowableRegExps[regExps[regExpsLen]]; + if (request.match(allow.regExp)) { + return allow; + } + } + return false; +} + /* * Deregister a module as 'allowed'. A subsequent 'require' for that module * will generate a warning that the module is not allowed, unless or until a @@ -285,14 +324,33 @@ function deregisterAllowables(mods) { }); } +/* + * Deregister a module as 'allowed' which has been registered as regexp. + * A subsequent 'require' for that module will generate a warning that + * the module is not allowed, unless or until a mock or substitute is + * registered for that module. + */ +function deregisterAllowableRegExp(regExp) { + if (registeredAllowableRegExps.hasOwnProperty(String(regExp))) { + var allow = registeredAllowableRegExps[regExp]; + if (allow.unhook) { + allow.paths.forEach(function (p) { + delete m._cache[p]; + }); + } + delete registeredAllowableRegExps[regExp]; + } +} + /* * Deregister all mocks, substitutes, and allowed modules, resetting the state * to a clean slate. This does not affect the enabled / disabled state of * mockery, though. */ function deregisterAll() { - Object.keys(registeredAllowables).forEach(function (mod) { - var allow = registeredAllowables[mod]; + Object.keys(registeredAllowables).concat(Object.keys(registeredAllowableRegExps)) + .forEach(function (mod) { + var allow = registeredAllowables[mod] || registeredAllowableRegExps[mod]; if (allow.unhook) { allow.paths.forEach(function (p) { delete m._cache[p]; @@ -303,6 +361,7 @@ function deregisterAll() { registeredMocks = {}; registeredSubstitutes = {}; registeredAllowables = {}; + registeredAllowableRegExps = {}; } // Exported functions @@ -315,8 +374,10 @@ exports.registerMock = registerMock; exports.registerSubstitute = registerSubstitute; exports.registerAllowable = registerAllowable; exports.registerAllowables = registerAllowables; +exports.registerAllowableRegExp = registerAllowableRegExp; exports.deregisterMock = deregisterMock; exports.deregisterSubstitute = deregisterSubstitute; exports.deregisterAllowable = deregisterAllowable; exports.deregisterAllowables = deregisterAllowables; +exports.deregisterAllowableRegExp = deregisterAllowableRegExp; exports.deregisterAll = deregisterAll; diff --git a/test/fixtures/$.object-assign.js b/test/fixtures/$.object-assign.js new file mode 100644 index 0000000..b8a1b36 --- /dev/null +++ b/test/fixtures/$.object-assign.js @@ -0,0 +1,3 @@ +exports.foo = function () { + return 'object assign'; +}; \ No newline at end of file diff --git a/test/logging-allowable.js b/test/logging-allowable.js index 8cb8aa1..d5ec1b0 100644 --- a/test/logging-allowable.js +++ b/test/logging-allowable.js @@ -27,6 +27,19 @@ var tests = { mock_console.verify(); mock_console.restore(); }, + "requiring the module which is allowed via regexp causes no warning to be logged": function () { + var mock_console, fake_module; + + mock_console = sinon.mock(console); + mock_console.expects('warn').never(); + + mockery.registerAllowableRegExp(/.+\$\.object-assign/ig); + fake_module = require('./fixtures/$.object-assign'); + assert.equal(fake_module.foo(), 'object assign'); + + mock_console.verify(); + mock_console.restore(); + }, "and the allowable is deregistered": { topic: function () { mockery.deregisterAllowable('./fixtures/fake_module'); @@ -41,6 +54,20 @@ var tests = { fake_module = require('./fixtures/fake_module'); assert.equal(fake_module.foo(), 'real foo'); + mock_console.verify(); + mock_console.restore(); + }, + "requiring the module which is allowed via regexp causes a warning to be logged": function () { + var mock_console, fake_module; + + mockery.deregisterAllowableRegExp(/.+\$\.object-assign/ig); + + mock_console = sinon.mock(console); + mock_console.expects('warn').once(); + + fake_module = require('./fixtures/$.object-assign'); + assert.equal(fake_module.foo(), 'object assign'); + mock_console.verify(); mock_console.restore(); } diff --git a/test/registered.js b/test/registered.js index 5dc1238..7615a40 100644 --- a/test/registered.js +++ b/test/registered.js @@ -27,6 +27,11 @@ var tests = { mockery.registerAllowable('./fixtures/fake_module'); var fake_module = require('./fixtures/fake_module'); assert.equal(fake_module.foo(), 'real foo'); + + mockery.registerAllowableRegExp(/.+\$\.object-assign/ig); + //require without a warning + var objectAssign = require('./fixtures/$.object-assign'); + assert.equal(objectAssign.foo(), 'object assign'); }, "and mockery is then disabled requiring the module returns the original module": function () { mockery.disable(); From 169943c1f82ba817fe87b049c9120905f00acb0d Mon Sep 17 00:00:00 2001 From: Vadim Ivanov Date: Wed, 4 Nov 2015 11:51:29 +0100 Subject: [PATCH 2/2] cleanup allowing modules via regexp --- README.md | 2 +- mockery.js | 14 ++++++++------ 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 5e91196..3e911bd 100644 --- a/README.md +++ b/README.md @@ -153,7 +153,7 @@ and similarly to deregister several modules at once, as you would expect: mockery.deregisterAllowables(['async', 'path', 'util']); -Sometimes it's useful to allow bunch of modules to be allowed without a warnings. +Sometimes it's useful to allow bunch of modules without a warnings. For example, when your code transpiled via babel. In that case you can allow them mockery.registerAllowableRegExp(/.+\$\.object-assign/ig); diff --git a/mockery.js b/mockery.js index 57cf647..4e4d49b 100644 --- a/mockery.js +++ b/mockery.js @@ -95,10 +95,12 @@ function hookedLoader(request, parent, isMain) { return subst.module; } - if ((registeredAllowables.hasOwnProperty(request) && ( - allow = registeredAllowables[request]) - ) || (allow = isRequesRegisteredAsRegexp(request)) - ) { + if (registeredAllowables.hasOwnProperty(request)) { + allow = registeredAllowables[request]; + } else { + allow = isRequesRegisteredAsRegexp(request); + } + if (allow) { if (allow.unhook) { file = m._resolveFilename(request, parent); if (file.indexOf('/') !== -1 && allow.paths.indexOf(file) === -1) { @@ -266,7 +268,7 @@ function registerAllowables(mods, unhook) { * not complain when it is loaded via 'require'. */ function registerAllowableRegExp(regExp, unhook) { - registeredAllowableRegExps[String(regExp)] = { + registeredAllowableRegExps[regExp] = { unhook: !!unhook, paths: [], regExp: regExp @@ -331,7 +333,7 @@ function deregisterAllowables(mods) { * registered for that module. */ function deregisterAllowableRegExp(regExp) { - if (registeredAllowableRegExps.hasOwnProperty(String(regExp))) { + if (registeredAllowableRegExps.hasOwnProperty(regExp)) { var allow = registeredAllowableRegExps[regExp]; if (allow.unhook) { allow.paths.forEach(function (p) {