From c375628dacd8043f60f5f1adae2debce49165648 Mon Sep 17 00:00:00 2001 From: Jason Walton Date: Fri, 2 Mar 2018 17:20:18 -0500 Subject: [PATCH] Resolve relative paths relative to the module under test (#190) --- lib/proxyquire.js | 66 +++++++++++++++++++------- test/proxyquire-relative-paths.js | 13 +++++ test/samples/relative-paths/a/index.js | 4 ++ test/samples/relative-paths/a/util.js | 1 + test/samples/relative-paths/b/index.js | 1 + test/samples/relative-paths/b/util.js | 1 + 6 files changed, 69 insertions(+), 17 deletions(-) create mode 100644 test/proxyquire-relative-paths.js create mode 100644 test/samples/relative-paths/a/index.js create mode 100644 test/samples/relative-paths/a/util.js create mode 100644 test/samples/relative-paths/b/index.js create mode 100644 test/samples/relative-paths/b/util.js diff --git a/lib/proxyquire.js b/lib/proxyquire.js index f1ac608..ba4e43f 100644 --- a/lib/proxyquire.js +++ b/lib/proxyquire.js @@ -2,8 +2,8 @@ /* jshint laxbreak:true, loopfunc:true */ var Module = require('module') +var path = require('path') var resolve = require('resolve') -var dirname = require('path').dirname var ProxyquireError = require('./proxyquire-error') var is = require('./is') var assert = require('assert') @@ -129,13 +129,47 @@ Proxyquire.prototype.load = function (request, stubs) { return this._withoutCache(this._parent, stubs, request, this._parent.require.bind(this._parent, request)) } +// Resolves a stub relative to a module. +// `baseModule` is the module we're resolving from. `pathToResolve` is the +// module we want to resolve (i.e. the string passed to `require()`). +Proxyquire.prototype._resolveModule = function (baseModule, pathToResolve) { + try { + return resolve.sync(pathToResolve, { + basedir: path.dirname(baseModule), + extensions: Object.keys(require.extensions), + paths: Module.globalPaths + }) + } catch (err) { + // If this is not a relative path (e.g. "foo" as opposed to "./foo"), and + // we couldn't resolve it, then we just let the path through unchanged. + // It's safe to do this, because if two different modules require "foo", + // they both expect to get back the same thing. + if (pathToResolve[0] !== '.') { + return pathToResolve + } + + // If `pathToResolve` is relative, then it is *not* safe to return it, + // since a file in one directory that requires "./foo" expects to get + // back a different module than one that requires "./foo" from another + // directory. However, if !this._preserveCache, then we don't want to + // throw, since we can resolve modules that don't exist. Resolve as + // best we can. + if (!this._preserveCache) { + return path.resolve(path.dirname(baseModule), pathToResolve) + } + + throw err + } +} + // 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') - if (hasOwnProperty.call(stubs, path)) { - var stub = stubs[path] + var resolvedPath = this._resolveModule(module.filename, path) + if (hasOwnProperty.call(stubs, resolvedPath)) { + var stub = stubs[resolvedPath] if (stub === null) { // Mimic the module-not-found exception thrown by node.js. @@ -163,6 +197,15 @@ Proxyquire.prototype._require = function (module, stubs, path) { 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) + var resolvedPath = Module._resolveFilename(path, module) + + // Resolve all stubs to absolute paths. + stubs = Object.keys(stubs) + .reduce(function (result, stubPath) { + var resolvedStubPath = this._resolveModule(resolvedPath, stubPath) + result[resolvedStubPath] = stubs[stubPath] + return result + }.bind(this), {}) // Override all require extension handlers var restoreExtensionHandlers = this._overrideExtensionHandlers(module, stubs) @@ -175,18 +218,7 @@ Proxyquire.prototype._withoutCache = function (module, stubs, path, func) { if (this._preserveCache) { restoreCache() } else { - var id = Module._resolveFilename(path, module) - var stubIds = Object.keys(stubs).map(function (stubPath) { - try { - return resolve.sync(stubPath, { - basedir: dirname(id), - extensions: Object.keys(require.extensions), - paths: Module.globalPaths - }) - } catch (_) {} - }) - var ids = [id].concat(stubIds.filter(Boolean)) - + var ids = [resolvedPath].concat(Object.keys(stubs).filter(Boolean)) ids.forEach(function (id) { delete require.cache[id] }) @@ -251,7 +283,7 @@ Proxyquire.prototype._disableModuleCache = function (path, module) { } } -Proxyquire.prototype._overrideExtensionHandlers = function (module, stubs) { +Proxyquire.prototype._overrideExtensionHandlers = function (module, resolvedStubs) { /* eslint node/no-deprecated-api: [error, {ignoreGlobalItems: ["require.extensions"]}] */ var originalExtensions = {} @@ -266,7 +298,7 @@ Proxyquire.prototype._overrideExtensionHandlers = function (module, stubs) { // 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) + module.require = self._require.bind(self, module, resolvedStubs) return originalExtensions[extension](module, filename) } diff --git a/test/proxyquire-relative-paths.js b/test/proxyquire-relative-paths.js new file mode 100644 index 0000000..9abab64 --- /dev/null +++ b/test/proxyquire-relative-paths.js @@ -0,0 +1,13 @@ +'use strict' + +/* jshint asi:true */ +/* global describe, it */ + +var proxyquire = require('..') + +describe('When requiring relative paths, they should be relative to the proxyrequired module', function () { + it('should return the correct result', function () { + var result = proxyquire('./samples/relative-paths/a/index.js', {'./util': {c: 'c'}}) + result.should.eql({a: 'a', c: 'c'}) + }) +}) diff --git a/test/samples/relative-paths/a/index.js b/test/samples/relative-paths/a/index.js new file mode 100644 index 0000000..1532193 --- /dev/null +++ b/test/samples/relative-paths/a/index.js @@ -0,0 +1,4 @@ +var util = require('./util') +require('../b') + +module.exports = util diff --git a/test/samples/relative-paths/a/util.js b/test/samples/relative-paths/a/util.js new file mode 100644 index 0000000..9852960 --- /dev/null +++ b/test/samples/relative-paths/a/util.js @@ -0,0 +1 @@ +exports.a = 'a' diff --git a/test/samples/relative-paths/b/index.js b/test/samples/relative-paths/b/index.js new file mode 100644 index 0000000..5f8ab40 --- /dev/null +++ b/test/samples/relative-paths/b/index.js @@ -0,0 +1 @@ +require('./util') diff --git a/test/samples/relative-paths/b/util.js b/test/samples/relative-paths/b/util.js new file mode 100644 index 0000000..378ac76 --- /dev/null +++ b/test/samples/relative-paths/b/util.js @@ -0,0 +1 @@ +exports.b = 'b'