Skip to content
This repository has been archived by the owner on Sep 23, 2021. 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 Jan 24, 2012
0 parents commit a813d18
Show file tree
Hide file tree
Showing 11 changed files with 362 additions and 0 deletions.
1 change: 1 addition & 0 deletions .gitignore
@@ -0,0 +1 @@
node_modules
24 changes: 24 additions & 0 deletions 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.
86 changes: 86 additions & 0 deletions README.md
@@ -0,0 +1,86 @@
# Sidedoor - Exposing a secondary API for your Node.js modules

As you've written unit tests for your Node.js modules, you've almost certainly
been forced to think about exposing some internal methods for the sole purpose
of enabling more comprehensive tests or verifying such tests. You've wrestled
with polluting the public API of your module with functions you really don't
want people to see, let alone depend on.

What if, instead, you could expose a secondary API for your module, such that
the additional methods are available to your tests, but without polluting the
public API?

This is just what Sidedoor enables. In your module, a simple call to Sidedoor
exposes whatever secondary API you need, while a similarly simple call from a
client module provides access to that secondary API.

## Installation

Just use npm:

npm install sidedoor

## Exposing an API

You expose your secondary API by telling Sidedoor which functions to expose,
and, optionally, the name under which you want to expose them. For example:

function doSomethingImportant() {
...
}

function doSomethingTestRelated() {
...
}

// Expose the primary API
module.exports = {
doSomething: doSomethingImportant
};

// Expose the secondary API
sidedoor.expose(module, 'test', {
testHelper: doSomethingTestRelated
});

Here, in addition to our module's primary API, we've exposed a secondary API
named 'test' that contains the single function `doSomethingTestRelated` exposed
under the name `testHelper`.

The arguments to `expose` are as follows:

* _module_, the module from which you are exposing the secondary API. This will
almost always be just 'module'.
* _name_, the name of the secondary API you are exposing. If omitted, a default
unnamed API is exposed.
* _exports_, the equivalent of the `exports` object, being the API that is to
be exposed.

Note that, because a secondary API may be named, it is possible to expose any
number of such APIs from any module.

## Accessing an API

You access a secondary API by asking Sidedoor to obtain it for you, optionally
providing the name of the API you want, and then simply calling it as you would
a regularly exported API. For example, :

var public_api = require('my_module'),
test_api = sidedoor.get('my_module', 'test');

// Call the public API as normal
public_api.doSomething();

// Call the secondary API obtained via Sidedoor
test_api.testHelper();

The arguments to `get` are as follows:

* _modpath_, the name or path of the module for which you want to access the API.
This is the same as what you would pass to `require` to obtain the primary API.
* _name_, the name of the secondary API you are accessing. If omitted, the default
unnamed API is obtained.

## License

Sidedoor is licensed under the [MIT License](http://github.com/mfncooper/sidedoor/raw/master/LICENSE).
60 changes: 60 additions & 0 deletions lib/sidedoor.js
@@ -0,0 +1,60 @@
/*
* Copyright (c) 2011 Yahoo! Inc. All rights reserved.
*/

/*
* A library that allows the controlled exposure of functions from a module
* without polluting the primary exports mechanism. This is useful mainly
* when exposing test-only functions, since those functions are made available
* to tests while being withheld from the public module API. (The functions are
* still available to any client, but an explicit effort must be made to obtain
* them.)
*/

var SIDEDOOR_KEY = "sidedoor",
DEFAULT_GROUP = "default";

/*
* Expose a set of functions from a module as a group. The group name may be
* elided, in which case the functions are considered to be within a default
* group.
*/
function expose(mod, group, fns) {
// Check for a defaulted group
if (typeof group !== "string") {
fns = group;
group = DEFAULT_GROUP;
}

if (!mod.hasOwnProperty(SIDEDOOR_KEY)) {
mod[SIDEDOOR_KEY] = {};
}
mod[SIDEDOOR_KEY][group] = fns;
}

/*
* Retrieve a set of functions from a module as a group. If the group name is
* not specified, the default group is retrieved. If the group of functions
* does not exist, undefined is returned.
*/
function get(modname, group) {
var mod = require.cache[require.resolve(modname)],
fns = null;

if (!mod) {
require(modname);
mod = require.cache[require.resolve(modname)];
}
if (!mod) {
throw new Error("bad stuff");
}

if (mod.hasOwnProperty(SIDEDOOR_KEY)) {
fns = mod[SIDEDOOR_KEY][group || DEFAULT_GROUP];
}

return fns;
}

exports.expose = expose;
exports.get = get;
30 changes: 30 additions & 0 deletions package.json
@@ -0,0 +1,30 @@
{
"author": "Martin Cooper <mfncooper@gmail.com>",
"name": "sidedoor",
"description": "Exposing a secondary API for your Node.js modules",
"version": "0.1.0",
"repository": {
"type": "git",
"url": "git://github.com/mfncooper/sidedoor.git"
},
"bugs": {
"url" : "http://github.com/mfncooper/sidedoor/issues"
},
"main": "lib/sidedoor.js",
"engines": {
"node": ">=0.4.5"
},
"dependencies": {},
"devDependencies": {
"nodeunit" : "0.6.x"
},
"scripts": {
"test": "test/run.js"
},
"licenses" : [
{
"type" : "MIT",
"url" : "http://github.com/mfncooper/mockery/raw/master/LICENSE"
}
]
}
9 changes: 9 additions & 0 deletions test/fixtures/exposeDefault.js
@@ -0,0 +1,9 @@
var sidedoor = require("../../lib/sidedoor.js");

function exposee() {
return "-exposed-";
}

sidedoor.expose(module, {
exposee: exposee
});
17 changes: 17 additions & 0 deletions test/fixtures/exposeMultiple.js
@@ -0,0 +1,17 @@
var sidedoor = require("../../lib/sidedoor.js");

function group1fn() {
return "-group1fn-exposed-";
}

function group2fn() {
return "-group2fn-exposed-";
}

sidedoor.expose(module, "mygroup1", {
group1fn: group1fn
});

sidedoor.expose(module, "mygroup2", {
group2fn: group2fn
});
9 changes: 9 additions & 0 deletions test/fixtures/exposeNamed.js
@@ -0,0 +1,9 @@
var sidedoor = require("../../lib/sidedoor.js");

function exposee() {
return "-exposed-";
}

sidedoor.expose(module, "mygroup", {
exposee: exposee
});
17 changes: 17 additions & 0 deletions test/fixtures/exposeNone.js
@@ -0,0 +1,17 @@
var sidedoor = require("../../lib/sidedoor.js");

function group1fn() {
return "-group1fn-exposed-";
}

function group2fn() {
return "-group2fn-exposed-";
}

sidedoor.expose(module, "mygroup1", {
group1fn: group1fn
});

sidedoor.expose(module, "mygroup2", {
group2fn: group2fn
});
3 changes: 3 additions & 0 deletions test/run.js
@@ -0,0 +1,3 @@
#!/usr/bin/env node
var test_reporter = require('nodeunit').reporters.nested;
test_reporter.run(['./test/test-sidedoor.js']);
106 changes: 106 additions & 0 deletions test/test-sidedoor.js
@@ -0,0 +1,106 @@
/*
* Copyright (c) 2011 Yahoo! Inc. All rights reserved.
*/

var testCase = require('nodeunit').testCase,
sidedoor = require('../lib/sidedoor');

function fixtureMod(name) {
return "../test/fixtures/" + name;
}

module.exports = testCase({

"when nothing is exposed": testCase({
"getting the default group returns undefined": function (test) {
var sd = sidedoor.get(fixtureMod("exposeNone"));
test.strictEqual(sd, undefined);
test.done();
},
"getting a named group returns undefined": function (test) {
var sd = sidedoor.get(fixtureMod("exposeNone"), "mygroup");
test.strictEqual(sd, undefined);
test.done();
}
}),

"when the default group is exposed": testCase({
"getting the default group returns the functions": function (test) {
var sd = sidedoor.get(fixtureMod("exposeDefault"));
test.notEqual(sd, undefined);
test.equal(typeof sd, "object");
test.equal(typeof sd.exposee, "function");
test.equal(sd.exposee(), "-exposed-");
test.done();
},
"getting a named group returns undefined": function (test) {
var sd = sidedoor.get(fixtureMod("exposeDefault"), "mygroup");
test.strictEqual(sd, undefined);
test.done();
}
}),

"when a named group is exposed": testCase({
"getting the default group returns undefined": function (test) {
var sd = sidedoor.get(fixtureMod("exposeNamed"));
test.strictEqual(sd, undefined);
test.done();
},
"getting the exposed group returns the functions": function (test) {
var sd = sidedoor.get(fixtureMod("exposeNamed"), "mygroup");
test.notEqual(sd, null);
test.equal(typeof sd, "object");
test.equal(typeof sd.exposee, "function");
test.equal(sd.exposee(), "-exposed-");
test.done();
},
"getting another named group returns undefined": function (test) {
var sd = sidedoor.get(fixtureMod("exposeNamed"), "another");
test.strictEqual(sd, undefined);
test.done();
}
}),

"when multiple groups are exposed": testCase({
"getting the default group returns undefined": function (test) {
var sd = sidedoor.get(fixtureMod("exposeMultiple"));
test.strictEqual(sd, undefined);
test.done();
},
"getting an exposed group returns the functions": function (test) {
var sd = sidedoor.get(fixtureMod("exposeMultiple"), "mygroup1");
test.notEqual(sd, null);
test.equal(typeof sd, "object");
test.equal(typeof sd.group1fn, "function");
test.equal(sd.group1fn(), "-group1fn-exposed-");
test.done();
},
"getting another exposed group returns the functions": function (test) {
var sd = sidedoor.get(fixtureMod("exposeMultiple"), "mygroup2");
test.notEqual(sd, null);
test.equal(typeof sd, "object");
test.equal(typeof sd.group2fn, "function");
test.equal(sd.group2fn(), "-group2fn-exposed-");
test.done();
},
"getting another named group returns undefined": function (test) {
var sd = sidedoor.get(fixtureMod("exposeMultiple"), "another");
test.strictEqual(sd, undefined);
test.done();
},
"functions are exposed only in their assigned groups": function (test) {
var sd1 = sidedoor.get(fixtureMod("exposeMultiple"), "mygroup1"),
sd2 = sidedoor.get(fixtureMod("exposeMultiple"), "mygroup2");
test.notEqual(sd1, null);
test.notEqual(sd2, null);
test.equal(typeof sd1, "object");
test.equal(typeof sd2, "object");
test.equal(typeof sd1.group1fn, "function");
test.equal(typeof sd2.group2fn, "function");
test.equal(typeof sd1.group2fn, "undefined");
test.equal(typeof sd2.group1fn, "undefined");
test.done();
}
})

});

0 comments on commit a813d18

Please sign in to comment.