This project is currently in BETA. APIs may be subject to frequent change prior to the 1.0 release. Please use very strict versioning in your package.json
if you choose to use candy-wrapper at this time.
Full API Documentation Available Here!
Questions, comments, bugs, suggestions, contributions, etc. are welcomed. Please submit an issue on GitHub.
Overview
This library provides the ability to wrap functions or properties so that you can monitor their behavior without changing it. Wrappers also allow you to decide when and how you want to change the behavior of a function or property. Candy-wrapper is similar to wonderful libraries like Sinon and shimmer and can be used for creating stubs, spys, and mocks for testing and monkey patching in production environments. The motivation behind creating candy-wrapper was that other libraries were too hard to learn, had inconsistent APIs, or generally couldn't do the things I needed to do with a wrapper. Hopefully candy-wrapper does everything anyone could ever hope for from a wrapper, or can be easily expanded to add new functionality.
The primary interfaces are Wrapper, Expect, Trigger, and Action:
- Wrappers wrap a function or property, so that when the Wrapper is called, so is the underlying function or property.
- You can use Expect to examine what happened with a Wrapper, such as seeing what args were passed to it or what the return value was.
- Triggers get called just before or just after a function call, providing the opportunity validate Expects or perform Actions.
- Actions, which are part of Triggers, can do things like change arguments recieved by the wrapped function or change the value returned to the caller.
If you are new to wrappers or candy-wrapper, a good place to start is the Getting Started Tutorial.
ES6
Note that this library currently makes exensive use of the features of JavaScript ES6, notably Proxies, classes, template literals, rest parameters, and the spread operator. For information about which platforms currently support ES6, see the ES6 compatibility table. It has been tested against Node 6 and seems to run on the latest versions of Chrome and Firefox.
Presumably it could be back-ported to ES5.1, perhaps drawing on the work of Sinon. The biggest challenge would be removing the use of Proxies. A pull request would be more than welcome if anyone wants to take that challenge on.
Installing and Using
node.js
Install:
$ npm install candy-wrapper
Use:
var Wrapper = require("candy-wrapper").Wrapper.
new Wrapper();
Browser
Note: I wouldn't recommend using candy-wrapper browser for now, but if you want to play around with it...
Install:
<script src=https://rawgit.com/apowers313/candy-wrapper/master/candy-wrapper.js></script>
Use:
<script>
var Wrapper = CandyWrapper.Wrapper;
</script>
Examples
Here are few example use cases to get you started with candy-wrapper...
Spys
Let's say that you have already written some code and now you want to test it to make sure it works right. Your very simple function square()
simply takes a number and squares it and you want to make sure it's working right.
var square = function(num) {
if (typeof num !== "number") {
throw new TypeError ("expected argument to be Number");
}
return num * num;
};
square = new Wrapper(square);
square(2);
square(4);
try {
square();
} catch(e) {}
square(3);
square(5);
square.historyList.filterFirst().expectReturn(4);
square.historyList.filterSecond().expectReturn(16);
square.historyList.filterThird().expectException(new TypeError ("expected argument to be Number"));
square.historyList.filterFourth().expectReturn(10);
square.historyList.filterFifth().expectReturn(23);
square.expectReportAllFailures();
Stubs
Let's say you're working on a big complex system that's eventually going to hook up to a database, but you really don't want to have to write all the database code before you can start working on the more interesting code that would rely on it. If only you could create a fake database object that behaved like the real thing...
var fakeDb = {};
fakeDb.getUser = new Wrapper();
fakeDb.listUsers = new Wrapper();
fakeDb.getUser.triggerOnArgs("apowers")
.actionReturn({
username: "apowers",
firstName: "Adam",
lastName: "Powers"
});
fakeDb.listUsers.triggerAlways()
.actionReturn([{
username: "apowers",
firstName: "Adam",
lastName: "Powers"
}, {
username: "bhope",
firstName: "Bob",
lastName: "Hope"
}]);
var user = fakeDb.getUser("apowers");
var userList = fakeDb.listUsers();
Mocks
Now let's say it's been a few months, and your system is almost completely done. You have completed your real database, and you're having some sort of error that you suspect is due to your database timing out. You want to simulate errors with your database, but it's not easy to figure out how to make it time out when you want it to. Instead, you can just wrap your database object in a mock and start injecting errors...
var mysqlDbInterface = {
getUser: function() {
}
};
var getUserWrapper = new Wrapper(mysqlDbInterface, "getUser");
getUserWrapper.triggerOnCallNumber(2)
.actionThrowException(new Error("Connection to mySQL timed out"));
Monkey Patching
Let's say that you are tired of Math.random() being a pseudo-random number generator and you want it to use your Digital Random Number Generator (DRNG) instead. Using candy-wrapper, you can monkey patch (that is, replace) Math.random()
with your DRNG:
new Wrapper(Math, "random", function() {
return 1.2345;
});
Math.random()
Wrapper.isWrapper(Math, "random");
Wrapper.unwrap(Math, "random");