├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── examples └── foobar │ ├── README.md │ ├── build.js │ ├── index.html │ ├── node_modules │ ├── browserify │ └── proxyquireify │ ├── package.json │ ├── src │ ├── bar.js │ └── foo.js │ └── test.js ├── index.js ├── lib ├── find-dependencies.js ├── prelude.js ├── replace-prelude.js └── transform.js ├── package.json ├── plugin.js └── test ├── clientside ├── argument-validation.js ├── falsy.js ├── independent-overrides.js ├── manipulating-overrides.js ├── noCallThru.js └── run.js ├── find-dependencies.js ├── fixtures ├── bar.js ├── dependencies.js ├── foo.js ├── stats.js ├── true.js ├── value.js └── var-declaration.js └── watchify-cli.js /.gitignore: -------------------------------------------------------------------------------- 1 | lib-cov 2 | *.seed 3 | *.log 4 | *.csv 5 | *.dat 6 | *.out 7 | *.pid 8 | *.gz 9 | 10 | pids 11 | logs 12 | results 13 | 14 | npm-debug.log 15 | node_modules 16 | 17 | bundle.js 18 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - '0.12' 4 | - iojs 5 | before_install: 6 | - npm install -g npm 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2013 Thorsten Lorenz. 2 | All rights reserved. 3 | 4 | Permission is hereby granted, free of charge, to any person 5 | obtaining a copy of this software and associated documentation 6 | files (the "Software"), to deal in the Software without 7 | restriction, including without limitation the rights to use, 8 | copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the 10 | Software is furnished to do so, subject to the following 11 | conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 18 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 20 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 21 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 22 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 23 | OTHER DEALINGS IN THE SOFTWARE. 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # proxyquireify [](http://travis-ci.org/thlorenz/proxyquireify) 2 | 3 | browserify `>= v2` version of [proxyquire](https://github.com/thlorenz/proxyquire). 4 | 5 | Proxies browserify's require in order to make overriding dependencies during testing easy while staying **totally unobstrusive**. To run your tests in both Node and the browser, use [proxyquire-universal](https://github.com/bendrucker/proxyquire-universal). 6 | 7 | 8 | 9 | **Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)* 10 | 11 | - [Features](#features) 12 | - [Installation](#installation) 13 | - [Example](#example) 14 | - [With Other Transforms](#with-other-transforms) 15 | - [API](#api) 16 | - [proxyquire.plugin()](#proxyquireplugin) 17 | - [proxyquire.browserify()](#proxyquirebrowserify) 18 | - [Deprecation Warning](#deprecation-warning) 19 | - [proxyquire(request: String, stubs: Object)](#proxyquirerequest-string-stubs-object) 20 | - [Important Magic](#important-magic) 21 | - [noCallThru](#nocallthru) 22 | - [More Examples](#more-examples) 23 | 24 | 25 | 26 | 27 | ## Features 28 | 29 | - **no changes to your code** are necessary 30 | - non overriden methods of a module behave like the original 31 | - mocking framework agnostic, if it can stub a function then it works with **proxyquireify** 32 | - "use strict" compliant 33 | - [automatic injection](https://github.com/thlorenz/proxyquireify#important-magic) of `require` calls to ensure the 34 | module you are testing gets bundled 35 | 36 | ## Installation 37 | 38 | npm install proxyquireify 39 | 40 | To use with browserify `< 5.1` please `npm install proxyquireify@0.5` instead. To run your tests in PhantomJS, you may need to [use a shim](https://github.com/bendrucker/phantom-ownpropertynames). 41 | 42 | ## Example 43 | 44 | **foo.js**: 45 | 46 | ```js 47 | var bar = require('./bar'); 48 | 49 | module.exports = function () { 50 | return bar.kinder() + ' ist ' + bar.wunder(); 51 | }; 52 | ``` 53 | 54 | **foo.test.js**: 55 | 56 | ```js 57 | var proxyquire = require('proxyquireify')(require); 58 | 59 | var stubs = { 60 | './bar': { 61 | wunder: function () { return 'wirklich wunderbar'; } 62 | , kinder: function () { return 'schokolade'; } 63 | } 64 | }; 65 | 66 | var foo = proxyquire('./src/foo', stubs); 67 | 68 | console.log(foo()); 69 | ``` 70 | 71 | **browserify.build.js**: 72 | 73 | ```js 74 | var browserify = require('browserify'); 75 | var proxyquire = require('proxyquireify'); 76 | 77 | browserify() 78 | .plugin(proxyquire.plugin) 79 | .require(require.resolve('./foo.test'), { entry: true }) 80 | .bundle() 81 | .pipe(fs.createWriteStream(__dirname + '/bundle.js')); 82 | ``` 83 | 84 | load it in the browser and see: 85 | 86 | schokolade ist wirklich wunderbar 87 | 88 | ## With Other Transforms 89 | 90 | If you're transforming your source code to JavaScript, you must apply those transforms before applying the proxyquireify plugin: 91 | 92 | ```js 93 | browserify() 94 | .transform('coffeeify') 95 | .plugin(proxyquire.plugin) 96 | .require(require.resolve('./test.coffee'), { entry: true }) 97 | .bundle() 98 | .pipe(fs.createWriteStream(__dirname + '/bundle.js')); 99 | ``` 100 | 101 | proxyquireify needs to parse your code looking for `require` statements. If you `require` anything that's not valid JavaScript that [acorn](https://github.com/marijnh/acorn) can parse (e.g. CoffeeScript, TypeScript), you need to make sure the relevant transform runs before proxyquireify. 102 | 103 | ## API 104 | 105 | ### proxyquire.plugin() 106 | 107 | **proxyquireify** functions as a browserify plugin and needs to be registered with browserify like so: 108 | 109 | ```js 110 | var browserify = require('browserify'); 111 | var proxyquire = require('proxyquireify'); 112 | 113 | browserify() 114 | .plugin(proxyquire.plugin) 115 | .require(require.resolve('./test'), { entry: true }) 116 | .bundle() 117 | .pipe(fs.createWriteStream(__dirname + '/bundle.js')); 118 | ``` 119 | 120 | Alternatively you can register **proxyquireify** as a plugin from the command line like so: 121 | 122 | ```sh 123 | browserify -p proxyquireify/plugin test.js > bundle.js 124 | ``` 125 | 126 | ### proxyquire.browserify() 127 | 128 | #### Deprecation Warning 129 | 130 | This API to setup **proxyquireify** was used prior to [browserify plugin](https://github.com/substack/node-browserify#bpluginplugin-opts) support. 131 | 132 | It has not been removed yet to make upgrading **proxyquireify** easier for now, but it **will be deprecated in future 133 | versions**. Please consider using the plugin API (above) instead. 134 | 135 | **** 136 | 137 | To be used in build script instead of `browserify()`, autmatically adapts browserify to work for tests and injects 138 | require overrides into all modules via a browserify transform. 139 | 140 | ```js 141 | proxyquire.browserify() 142 | .require(require.resolve('./test'), { entry: true }) 143 | .bundle() 144 | .pipe(fs.createWriteStream(__dirname + '/bundle.js')); 145 | ``` 146 | 147 | **** 148 | 149 | ### proxyquire(request: String, stubs: Object) 150 | 151 | - **request**: path to the module to be tested e.g., `../lib/foo` 152 | - **stubs**: key/value pairs of the form `{ modulePath: stub, ... }` 153 | - module paths are relative to the tested module **not** the test file 154 | - therefore specify it exactly as in the require statement inside the tested file 155 | - values themselves are key/value pairs of functions/properties and the appropriate override 156 | 157 | ```js 158 | var proxyquire = require('proxyquireify')(require); 159 | var barStub = { wunder: function () { 'really wonderful'; } }; 160 | 161 | var foo = proxyquire('./foo', { './bar': barStub }) 162 | ``` 163 | 164 | #### Important Magic 165 | 166 | In order for browserify to include the module you are testing in the bundle, **proxyquireify** will inject a 167 | `require()` call for every module you are proxyquireing. So in the above example `require('./foo')` will be injected at 168 | the top of your test file. 169 | 170 | ### noCallThru 171 | 172 | By default **proxyquireify** calls the function defined on the *original* dependency whenever it is not found on the stub. 173 | 174 | If you prefer a more strict behavior you can prevent *callThru* on a per module or per stub basis. 175 | 176 | If *callThru* is disabled, you can stub out modules that weren't even included in the bundle. **Note**, that unlike in 177 | proxquire, there is no option to prevent call thru globally. 178 | 179 | ```js 180 | // Prevent callThru for path module only 181 | var foo = proxyquire('./foo', { 182 | path: { 183 | extname: function (file) { ... } 184 | , '@noCallThru': true 185 | } 186 | , fs: { readdir: function (..) { .. } } 187 | }); 188 | 189 | // Prevent call thru for all contained stubs (path and fs) 190 | var foo = proxyquire('./foo', { 191 | path: { 192 | extname: function (file) { ... } 193 | } 194 | , fs: { readdir: function (..) { .. } } 195 | , '@noCallThru': true 196 | }); 197 | 198 | // Prevent call thru for all stubs except path 199 | var foo = proxyquire('./foo', { 200 | path: { 201 | extname: function (file) { ... } 202 | , '@noCallThru': false 203 | } 204 | , fs: { readdir: function (..) { .. } } 205 | , '@noCallThru': true 206 | }); 207 | ``` 208 | 209 | ## More Examples 210 | 211 | - [foobar](https://github.com/thlorenz/proxyquireify/tree/master/examples/foobar) 212 | -------------------------------------------------------------------------------- /examples/foobar/README.md: -------------------------------------------------------------------------------- 1 | # foobar example 2 | 3 | Shows how `bar.wunder` function and non-existent `bar.kinder` are overridden. 4 | 5 | ## Run 6 | 7 | npm explore proxyquireify 8 | cd examples/foobar 9 | npm install && npm test 10 | -------------------------------------------------------------------------------- /examples/foobar/build.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs') 2 | , proxyquire = require('proxyquireify') 3 | , browserify = require('browserify') 4 | ; 5 | 6 | browserify({ debug: true }) 7 | .plugin(proxyquire.plugin) 8 | .require(require.resolve('./test'), { entry: true }) 9 | .bundle() 10 | .pipe(fs.createWriteStream(__dirname + '/bundle.js')); 11 | -------------------------------------------------------------------------------- /examples/foobar/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | 5 |It should say "schokolade ist wirklich wunderbar" in the console
11 | 12 | 13 | -------------------------------------------------------------------------------- /examples/foobar/node_modules/browserify: -------------------------------------------------------------------------------- 1 | ../../../node_modules/browserify -------------------------------------------------------------------------------- /examples/foobar/node_modules/proxyquireify: -------------------------------------------------------------------------------- 1 | ../../../ -------------------------------------------------------------------------------- /examples/foobar/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "foobar", 3 | "version": "0.0.0", 4 | "description": "proxyquireify foobar example", 5 | "scripts": { 6 | "test": "node build && open index.html" 7 | }, 8 | "dependencies": { 9 | }, 10 | "devDependencies": {}, 11 | "keywords": [], 12 | "author": { 13 | "name": "Thorsten Lorenz", 14 | "email": "thlorenz@gmx.de", 15 | "url": "http://thlorenz.com" 16 | }, 17 | "license": "MIT", 18 | "engine": { 19 | "node": ">=0.6" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /examples/foobar/src/bar.js: -------------------------------------------------------------------------------- 1 | exports.wunder = function () { 2 | return 'wunderbar'; 3 | }; 4 | -------------------------------------------------------------------------------- /examples/foobar/src/foo.js: -------------------------------------------------------------------------------- 1 | var bar = require('./bar'); 2 | 3 | module.exports = function () { 4 | return bar.kinder() + ' ist ' + bar.wunder(); 5 | }; 6 | -------------------------------------------------------------------------------- /examples/foobar/test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var proxyquire = require('proxyquireify')(require); 4 | 5 | var stubs = { 6 | './bar': { 7 | wunder: function () { return 'wirklich wunderbar'; } 8 | , kinder: function () { return 'schokolade'; } 9 | } 10 | }; 11 | 12 | var foo = proxyquire('./src/foo', stubs); 13 | console.log(foo()); 14 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var fillMissingKeys = require('fill-keys'); 4 | var moduleNotFoundError = require('module-not-found-error'); 5 | 6 | function ProxyquireifyError(msg) { 7 | this.name = 'ProxyquireifyError'; 8 | Error.captureStackTrace(this, ProxyquireifyError); 9 | this.message = msg || 'An error occurred inside proxyquireify.'; 10 | } 11 | 12 | function validateArguments(request, stubs) { 13 | var msg = (function getMessage() { 14 | if (!request) 15 | return 'Missing argument: "request". Need it to resolve desired module.'; 16 | 17 | if (!stubs) 18 | return 'Missing argument: "stubs". If no stubbing is needed, use regular require instead.'; 19 | 20 | if (typeof request != 'string') 21 | return 'Invalid argument: "request". Needs to be a requirable string that is the module to load.'; 22 | 23 | if (typeof stubs != 'object') 24 | return 'Invalid argument: "stubs". Needs to be an object containing overrides e.g., {"path": { extname: function () { ... } } }.'; 25 | })(); 26 | 27 | if (msg) throw new ProxyquireifyError(msg); 28 | } 29 | 30 | var stubs; 31 | 32 | function stub(stubs_) { 33 | stubs = stubs_; 34 | // This cache is used by the prelude as an alternative to the regular cache. 35 | // It is not read or written here, except to set it to an empty object when 36 | // adding stubs and to reset it to null when clearing stubs. 37 | module.exports._cache = {}; 38 | } 39 | 40 | function reset() { 41 | stubs = undefined; 42 | module.exports._cache = null; 43 | } 44 | 45 | var proxyquire = module.exports = function (require_) { 46 | if (typeof require_ != 'function') 47 | throw new ProxyquireifyError( 48 | 'It seems like you didn\'t initialize proxyquireify with the require in your test.\n' 49 | + 'Make sure to correct this, i.e.: "var proxyquire = require(\'proxyquireify\')(require);"' 50 | ); 51 | 52 | reset(); 53 | 54 | return function(request, stubs) { 55 | 56 | validateArguments(request, stubs); 57 | 58 | // set the stubs and require dependency 59 | // when stub require is invoked by the module under test it will find the stubs here 60 | stub(stubs); 61 | var dep = require_(request); 62 | reset(); 63 | 64 | return dep; 65 | }; 66 | }; 67 | 68 | // Start with the default cache 69 | proxyquire._cache = null; 70 | 71 | proxyquire._proxy = function (require_, request) { 72 | function original() { 73 | return require_(request); 74 | } 75 | 76 | if (!stubs || !stubs.hasOwnProperty(request)) return original(); 77 | 78 | var stub = stubs[request]; 79 | 80 | if (stub === null) throw moduleNotFoundError(request) 81 | 82 | var stubWideNoCallThru = Boolean(stubs['@noCallThru']) && (stub == null || stub['@noCallThru'] !== false); 83 | var noCallThru = stubWideNoCallThru || (stub != null && Boolean(stub['@noCallThru'])); 84 | return noCallThru ? stub : fillMissingKeys(stub, original()); 85 | }; 86 | 87 | if (require.cache) { 88 | // only used during build, so prevent browserify from including it 89 | var replacePreludePath = './lib/replace-prelude'; 90 | var replacePrelude = require(replacePreludePath); 91 | proxyquire.browserify = replacePrelude.browserify; 92 | proxyquire.plugin = replacePrelude.plugin; 93 | } 94 | -------------------------------------------------------------------------------- /lib/find-dependencies.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var detective = require('detective'); 4 | var hasRequire = require('has-require'); 5 | 6 | function simpleRequire(n) { 7 | // var proxy = require('proxyquireify') 8 | return n.parent 9 | && n.parent.id 10 | && n.parent.id.name; 11 | } 12 | 13 | function requireWithImmediateCall(n) { 14 | // var proxy = require('proxyquireify')(require) 15 | var p = n.parent; 16 | return p.parent 17 | && p.parent.id 18 | && p.parent.id.name; 19 | } 20 | 21 | function requireWithImmediateCallWithoutVar(n) { 22 | // proxy = require('proxyquireify')(require) 23 | var p = n.parent; 24 | return p.parent 25 | && p.parent.left 26 | && p.parent.left.name; 27 | } 28 | 29 | function findProxyquireVars(src) { 30 | return detective 31 | .find(src, { nodes: true }).nodes 32 | .map(function (n) { 33 | var arg = n.arguments[0]; 34 | return arg 35 | && arg.value === 'proxyquireify' 36 | && arg.type === 'Literal' 37 | && ( simpleRequire(n) || requireWithImmediateCall(n) || requireWithImmediateCallWithoutVar(n) ); 38 | }) 39 | .filter(function (n) { return n; }) 40 | ; 41 | } 42 | 43 | module.exports = function(src) { 44 | if (!hasRequire(src, 'proxyquireify')) return []; 45 | 46 | // use hash to get unique values 47 | var hash = findProxyquireVars(src) 48 | .map(function (name) { 49 | return detective(src, { word: name }); 50 | }) 51 | .reduce(function (acc, arr) { 52 | arr.forEach(function (x) { acc[x] = true;}); 53 | return acc; 54 | }, {}); 55 | 56 | return Object.keys(hash); 57 | }; 58 | -------------------------------------------------------------------------------- /lib/prelude.js: -------------------------------------------------------------------------------- 1 | // modules are defined as an array 2 | // [ module function, map of requireuires ] 3 | // 4 | // map of requireuires is short require name -> numeric require 5 | // 6 | // anything defined in a previous bundle is accessed via the 7 | // orig method which is the requireuire for previous bundles 8 | 9 | (function outer (modules, cache, entry) { 10 | // Save the require from previous bundle to this closure if any 11 | var previousRequire = typeof require == "function" && require; 12 | 13 | function findProxyquireifyName() { 14 | var deps = Object.keys(modules) 15 | .map(function (k) { return modules[k][1]; }); 16 | 17 | for (var i = 0; i < deps.length; i++) { 18 | var pq = deps[i]['proxyquireify']; 19 | if (pq) return pq; 20 | } 21 | } 22 | 23 | var proxyquireifyName = findProxyquireifyName(); 24 | 25 | function newRequire(name, jumped){ 26 | // Find the proxyquireify module, if present 27 | var pqify = (proxyquireifyName != null) && cache[proxyquireifyName]; 28 | 29 | // Proxyquireify provides a separate cache that is used when inside 30 | // a proxyquire call, and is set to null outside a proxyquire call. 31 | // This allows the regular caching semantics to work correctly both 32 | // inside and outside proxyquire calls while keeping the cached 33 | // modules isolated. 34 | // When switching from one proxyquire call to another, it clears 35 | // the cache to prevent contamination between different sets 36 | // of stubs. 37 | var currentCache = (pqify && pqify.exports._cache) || cache; 38 | 39 | if(!currentCache[name]) { 40 | if(!modules[name]) { 41 | // if we cannot find the the module within our internal map or 42 | // cache jump to the current global require ie. the last bundle 43 | // that was added to the page. 44 | var currentRequire = typeof require == "function" && require; 45 | if (!jumped && currentRequire) return currentRequire(name, true); 46 | 47 | // If there are other bundles on this page the require from the 48 | // previous one is saved to 'previousRequire'. Repeat this as 49 | // many times as there are bundles until the module is found or 50 | // we exhaust the require chain. 51 | if (previousRequire) return previousRequire(name, true); 52 | var err = new Error('Cannot find module \'' + name + '\''); 53 | err.code = 'MODULE_NOT_FOUND'; 54 | throw err; 55 | } 56 | var m = currentCache[name] = {exports:{}}; 57 | 58 | // The normal browserify require function 59 | var req = function(x){ 60 | var id = modules[name][1][x]; 61 | return newRequire(id ? id : x); 62 | }; 63 | 64 | // The require function substituted for proxyquireify 65 | var moduleRequire = function(x){ 66 | var pqify = (proxyquireifyName != null) && cache[proxyquireifyName]; 67 | // Only try to use the proxyquireify version if it has been `require`d 68 | if (pqify && pqify.exports._proxy) { 69 | return pqify.exports._proxy(req, x); 70 | } else { 71 | return req(x); 72 | } 73 | }; 74 | 75 | modules[name][0].call(m.exports,moduleRequire,m,m.exports,outer,modules,currentCache,entry); 76 | } 77 | return currentCache[name].exports; 78 | } 79 | for(var i=0;i