Skip to content
This repository has been archived by the owner on Apr 30, 2024. It is now read-only.

Commit

Permalink
Initial commit.
Browse files Browse the repository at this point in the history
  • Loading branch information
mfncooper committed Oct 11, 2011
0 parents commit d230816
Show file tree
Hide file tree
Showing 4 changed files with 416 additions and 0 deletions.
24 changes: 24 additions & 0 deletions LICENSE
Original file line number Original file line Diff line number Diff line change
@@ -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 changes: 145 additions & 0 deletions README.md
Original file line number Original file line Diff line number Diff line change
@@ -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).
222 changes: 222 additions & 0 deletions mockery.js
Original file line number Original file line Diff line number Diff line change
@@ -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;
Loading

0 comments on commit d230816

Please sign in to comment.