Permalink
Browse files

Initial commit.

  • Loading branch information...
0 parents commit d2308161e82cd28e25ed6ed66801e82ffe8a8dd9 @mfncooper committed Oct 11, 2011
Showing with 416 additions and 0 deletions.
  1. +24 −0 LICENSE
  2. +145 −0 README.md
  3. +222 −0 mockery.js
  4. +25 −0 package.json
24 LICENSE
@@ -0,0 +1,24 @@
+ Copyrights for code authored by Yahoo! Inc. is licensed under the following
+ terms:
+
+ MIT License
+
+ Copyright (c) 2011 Yahoo! Inc. All Rights Reserved.
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to
+ deal in the Software without restriction, including without limitation the
+ rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ sell copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in
+ all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ DEALINGS IN THE SOFTWARE.
145 README.md
@@ -0,0 +1,145 @@
+# Mockery - Simplifying the use of mocks with Node.js
+
+If you've tried working with mocks in Node.js, you've no doubt discovered that
+it's not so easy to get your mocks hooked up in the face of Node's module
+loading system. When your source-under-test pulls in its dependencies through
+`require`, you want your mocks provided, instead of the original module,
+to enable true unit testing of your code.
+
+This is exactly the problem Mockery is designed to solve. Mockery gives you a
+simple and easy to use API with which you can hook in your mocks without having
+to get your hands dirty with the `require` cache or other Node implementation
+details.
+
+Mockery is *not* a mocking framework. It lets you work more easily with your
+framework of choice (or no framework) to get your mocks hooked in to all the
+right places in the code you need to test.
+
+## Installation
+
+Just use npm:
+
+ npm install mockery
+
+## Enabling mockery
+
+When enabled, Mockery intercepts *all* `require` calls, regardless of where
+those calls are being made from. Thus it's almost always desirable to bracket
+your usage as narrowly as possible.
+
+If you're using a typical unit testing framework, you might enable and disable
+Mockery in the test setup and teardown functions for your test cases. Something
+like this:
+
+ setUp: function() {
+ mockery.enable();
+ },
+ tearDown: function() {
+ mockery.disable();
+ }
+
+## Registering mocks
+
+You register your mocks with Mockery to tell it which mocks to provide for which
+`require` calls. For example:
+
+ var fsMock = {
+ stat: function (path, cb) { /* your mock code */ }
+ };
+ mockery.registerMock('fs', fsMock);
+
+The arguments to `registerMock` are as follows:
+
+* _module_, the name or path of the module for which a mock is being
+registered. This must exactly match the argument to `require`; there is no
+"clever" matching.
+* _mock_, the mock to be provided. Whatever is provided here is what will
+become the result of subsequent `require` calls; that is, the `exports` of the
+module.
+
+If you no longer want your mock to be used, you can deregister it:
+
+ mockery.deregisterMock('fs');
+
+Now the original module will be provided for any subsequent `require` calls.
+
+## Registering substitutes
+
+Sometimes you want to implement your mock itself as a module, especially if it's
+more complicated and you'll be reusing it more widely. In that case, you can
+tell Mockery to substitute that module for the original one. For example:
+
+ mockery.registerSubstitute('fs', 'fs-mock');
+
+Now any `require` invocation for 'fs' will be satisfied by loading the 'fs-mock'
+module instead.
+
+The arguments to `registerSubstitute` are as follows:
+
+* _module_, the name or path of the module for which a substitute is being
+registered. This must exactly match the argument to `require`; there is no
+"clever" matching.
+* _substitute_, the name or path of the module to substitute for _module_.
+
+If you no longer want your substitute to be used, you can deregister it:
+
+ mockery.deregisterSubstitute('fs');
+
+Now the original module will be provided for any subsequent `require` calls.
+
+## Registering allowable modules
+
+If you enable Mockery and _don't_ mock or substitute a module that is later
+loaded via `require`, Mockery will print a warning to the console to tell you
+that. This is so that you don't inadvertently use downstream modules without
+being aware of them. By registering a module as "allowable", you tell Mockery
+that you know about its use, and then Mockery won't print the warning.
+
+The most common use case for this is your source-under-test, which obviously
+you'll want to load without warnings. For example:
+
+ mockery.registerAllowable('./my-source-under-test');
+
+As with `registerMock` and `registerSubstitute`, the first argument, _module_,
+is the name or path of the module as it would be provided to `require`. Once
+again, you can deregister it if you need to:
+
+ mockery.deregisterAllowable('./my-source-under-test');
+
+### Unhooking
+
+By default, the Node module loader will load a given module only once, caching
+the loaded module for the lifetime of the process. When you're using Mockery,
+this is almost always what you want. _Almost_. In relatively rare situations,
+you may find that you need to use different mocks for different test cases
+for the same source-under-test. (This is not the same as supplying different
+test data in the same mock; here we're talking about providing different
+functions for a module's `exports`.)
+
+To do this, your source-under-test must be unhooked from Node's module loading
+system, such that it can be loaded again with new mocks. You do this by passing
+a second argument, _unhook_, to `registerAllowable`, like this:
+
+ mockery.registerAllowable('./my-source-under-test', true);
+
+When you subsequently deregister your source-under-test, Mockery will unhook it
+from the Node module loading system as well as deregistering it.
+
+## Deregistering everything
+
+Since it's such a common use case, especially when you're using a unit test
+framework and its setup and teardown functions, Mockery provides a convenience
+function to deregister everything:
+
+ mockery.deregisterAll();
+
+This will deregister all mocks, substitutes, and allowable modules, as well as
+unhooking any hooked modules.
+
+## The name
+
+Mockery is to mocks as rookery is to rooks.
+
+## License
+
+Mockery is licensed under the [MIT License](http://github.com/mfncooper/mockery/raw/master/LICENSE).
@@ -0,0 +1,222 @@
+/*
+ Copyrights for code authored by Yahoo! Inc. is licensed under the following
+ terms:
+
+ MIT License
+
+ Copyright (c) 2011 Yahoo! Inc. All Rights Reserved.
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to
+ deal in the Software without restriction, including without limitation the
+ rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ sell copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in
+ all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ DEALINGS IN THE SOFTWARE.
+*/
+
+/*
+ * A library that enables the hooking of the standard 'require' function, such
+ * that a (possibly partial) mock implementation can be provided instead. This
+ * is most useful for running unit tests, since any dependency obtained through
+ * 'require' can be mocked out.
+ */
+
+var m = require('module'),
+ registeredMocks = {},
+ registeredSubstitutes = {},
+ registeredAllowables = {},
+ originalLoader = null;
+
+/*
+ * The (private) loader replacement that is used when hooking is enabled. It
+ * does the work of returning a mock or substitute when configured, reporting
+ * non-allowed modules, and invoking the original loader when appropriate.
+ * The signature of this function *must* match that of Node's Module._load,
+ * since it will replace that when mockery is enabled.
+ */
+function hookedLoader(request, parent, isMain) {
+ var subst, allow, file;
+
+ if (!originalLoader) {
+ throw new Error("Loader has not been hooked");
+ }
+
+ if (registeredMocks.hasOwnProperty(request)) {
+ return registeredMocks[request];
+ } else if (registeredSubstitutes.hasOwnProperty(request)) {
+ subst = registeredSubstitutes[request];
+ if (!subst.module && subst.name) {
+ subst.module = originalLoader(subst.name, parent, isMain);
+ }
+ if (!subst.module) {
+ throw new Error("Misconfigured substitute for '" + request + "'");
+ }
+ return subst.module;
+ } else {
+ if (!registeredAllowables.hasOwnProperty(request)) {
+ console.warn("WARNING: loading non-allowed module: " + request);
+ } else {
+ allow = registeredAllowables[request];
+ if (allow.unhook) {
+ file = m._resolveFilename(request, parent)[1];
+ if (file.indexOf('/') !== -1 && allow.paths.indexOf(file) === -1) {
+ allow.paths.push(file);
+ }
+ }
+ }
+ return originalLoader(request, parent, isMain);
+ }
+}
+
+/*
+ * Enables mockery by hooking subsequent 'require' invocations. Note that *all*
+ * 'require' invocations will be hooked until 'disable' is called. Calling this
+ * function more than once will have no ill effects.
+ */
+function enable() {
+ if (originalLoader) {
+ // Already hooked
+ return;
+ }
+ /*jslint nomen: true */
+ originalLoader = m._load;
+ m._load = hookedLoader;
+}
+
+/*
+ * Disables mockery by unhooking from the Node loader. No subsequent 'require'
+ * invocations will be seen by mockery. Calling this function more than once
+ * will have no ill effects.
+ */
+function disable() {
+ if (!originalLoader) {
+ // Not hooked
+ return;
+ }
+ /*jslint nomen: true */
+ m._load = originalLoader;
+ originalLoader = null;
+}
+
+/*
+ * Register a mock object for the specified module. While mockery is enabled,
+ * any subsequent 'require' for this module will return the mock object. The
+ * mock need not mock out all original exports, but no fallback is provided
+ * for anything not mocked and subsequently invoked.
+ */
+function registerMock(mod, mock) {
+ if (registeredMocks.hasOwnProperty(mod)) {
+ console.warn("WARNING: Replacing existing mock for module: " + mod);
+ }
+ registeredMocks[mod] = mock;
+}
+
+/*
+ * Deregister a mock object for the specified module. A subsequent 'require' for
+ * that module will revert to the previous behaviour (which, by default, means
+ * falling back to the original 'require' behaviour).
+ */
+function deregisterMock(mod) {
+ if (registeredMocks.hasOwnProperty(mod)) {
+ delete registeredMocks[mod];
+ }
+}
+
+/*
+ * Register a substitute module for the specified module. While mockery is
+ * enabled, any subsequent 'require' for this module will be effectively
+ * replaced by a 'require' for the substitute module. This is useful when
+ * a mock implementation is itself implemented as a module.
+ */
+function registerSubstitute(mod, subst) {
+ if (registeredSubstitutes.hasOwnProperty(mod)) {
+ console.warn("WARNING: Replacing existing substiute for module: " + mod);
+ }
+ registeredSubstitutes[mod] = subst;
+}
+
+/*
+ * Deregister a substitute module for the specified module. A subsequent
+ * 'require' for that module will revert to the previous behaviour (which, by
+ * default, means falling back to the original 'require' behaviour).
+ */
+function deregisterSubstitute(mod) {
+ if (registeredSubstitutes.hasOwnProperty(mod)) {
+ delete registeredSubstitutes[mod];
+ }
+}
+
+/*
+ * Register a module as 'allowed', meaning that, even if a mock or substitute
+ * for it has not been registered, mockery will not complain when it is loaded
+ * via 'require'. This encourages the user to consciously declare the modules
+ * that will be loaded and used in the original form, thus avoiding warnings.
+ *
+ * If 'unhook' is true, the module will be removed from the module cache when
+ * it is deregistered.
+ */
+function registerAllowable(mod, unhook) {
+ registeredAllowables[mod] = {
+ unhook: !!unhook,
+ paths: []
+ };
+}
+
+/*
+ * 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
+ * mock or substitute is registered for that module.
+ */
+function deregisterAllowable(mod) {
+ if (registeredAllowables.hasOwnProperty(mod)) {
+ var allow = registeredAllowables[mod];
+ if (allow.unhook) {
+ allow.paths.forEach(function (p) {
+ delete m._cache[p];
+ });
+ }
+ delete registeredAllowables[mod];
+ }
+}
+
+/*
+ * 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];
+ if (allow.unhook) {
+ allow.paths.forEach(function (p) {
+ delete m._cache[p];
+ });
+ }
+ });
+
+ registeredMocks = {};
+ registeredSubstitutes = {};
+ registeredAllowables = {};
+}
+
+// Exported functions
+exports.enable = enable;
+exports.disable = disable;
+exports.registerMock = registerMock;
+exports.registerSubstitute = registerSubstitute;
+exports.registerAllowable = registerAllowable;
+exports.deregisterMock = deregisterMock;
+exports.deregisterSubstitute = deregisterSubstitute;
+exports.deregisterAllowable = deregisterAllowable;
+exports.deregisterAll = deregisterAll;
Oops, something went wrong.

0 comments on commit d230816

Please sign in to comment.