Skip to content

Commit

Permalink
Initial commit of sinon test spy/stubbing/mocking library
Browse files Browse the repository at this point in the history
  • Loading branch information
cjohansen authored and Christian Johansen committed Mar 29, 2010
0 parents commit 0feec9f
Show file tree
Hide file tree
Showing 8 changed files with 860 additions and 0 deletions.
8 changes: 8 additions & 0 deletions jsTestDriver.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
server: http://localhost:4224

load:
- lib/*.js
- src/sinon.js
- src/spy.js
- src/*.js
- test/*.js
109 changes: 109 additions & 0 deletions lib/object.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
(function () {
var isOwnProperty = (function () {
var hasOwn = Object.prototype.hasOwnProperty;

if (typeof hasOwn == "function" && Function.prototype.call) {
return function (object, property) {
return hasOwn.call(object, property);
};
} else {
return function (object, property) {
var value = object[property];

if (typeof value == "undefined") {
return false;
}

return Object.prototype[property] !== value;
};
}
}());

if (!Object.each) {
Object.each = (function () {
// Troublesome object properties
var oDontEnums = ["toString", "toLocaleString", "valueOf",
"hasOwnProperty", "isPrototypeOf",
"constructor", "propertyIsEnumerable"];

// Troublesome function properties
var fDontEnums = ["call", "apply", "prototype"];
fDontEnums = oDontEnums.concat(fDontEnums);

var needsFix = true;
var object = { toString: 1 };

// If we can loop this property, we don't need the fix
// This test is only performed once - when the function is
// defined
for (var prop in object) {
if (isOwnProperty(object, prop)) {
needsFix = false;
}
}

// The each function
return function (object, callback) {
if (typeof callback != "function") {
throw new TypeError("Please provide a callback");
}

if (!object) {
return;
}

// Normal loop, should expose all enumerable properties
// in conforming browsers
for (var prop in object) {
if (isOwnProperty(object, prop)) {
callback(prop, object[prop]);
}
}

// Internet Explorer
if (needsFix) {
var properties = typeof object == "function" ?
fDontEnums : oDontEnums;
var property;

for (var i = 0, l = properties.length; i < l; i++) {
property = properties[i];

if (isOwnProperty(object, property)) {
callback(property, object[property]);
}
}
}
}
}());
}

if (!Object.extend) {
Object.extend = (function () {
function extend (target, source) {
target = target || {};

if (!source) {
return target;
}

Object.each(source, function (prop, val) {
target[prop] = val;
});

return target;
}

return extend;
}());
}

if (!Object.create) {
Object.create = function (object) {
function F () {};
F.prototype = object;

return new F;
};
}
}());
3 changes: 3 additions & 0 deletions src/sinon.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
var sinon = (function () {
return {};
}());
161 changes: 161 additions & 0 deletions src/spy.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
(function () {
function spy (func) {
return spy.create(func);
}

Object.extend(spy, (function () {
function create (func) {
if (typeof func != "function") {
throw new TypeError("spy needs a function to spy on");
}

function proxy () {
return proxy.invoke(func, this, arguments);
}

Object.extend(proxy, spy);
delete proxy.create;
Object.extend(proxy, func);

return proxy;
}

function invoke (func, thisObj, args) {
if (!this.calls) {
this.calls = [];
}

var call = spyCall.create(thisObj, args);

try {
call.returnValue = func.apply(thisObj, args);
} catch (e) {
call.exception = e;
throw e;
} finally {
this.calls.push(call);
}

return call.returnValue;
}

function getCall (i) {
return this.calls && this.calls[i];
}

function called () {
return this.callCount() > 0;
}

function callCount () {
return this.calls && this.calls.length;
}

function calledOn (thisObj) {
return matchAnyCall(this, "calledOn", arguments);
}

function calledWith () {
return matchAnyCall(this, "calledWith", arguments);
}

function calledWithExactly () {
return matchAnyCall(this, "calledWithExactly", arguments);
}

function threw (error) {
return matchAnyCall(this, "threw", arguments);
}

function matchAnyCall (proxy, method, args) {
if (!proxy.calls) {
return false;
}

var spyCall;

for (var i = 0, l = proxy.calls.length; i < l; i++) {
spyCall = proxy.calls[i];

if (spyCall[method].apply(spyCall, args)) {
return true;
}
}

return false;
}

return {
create: create,
called: called,
calledOn: calledOn,
calledWith: calledWith,
calledWithExactly: calledWithExactly,
callCount: callCount,
getCall: getCall,
invoke: invoke,
threw: threw
};
}()));

var spyCall = (function () {
function calledOn (thisObj) {
return this.thisObj === thisObj;
}

function calledWith () {
for (var i = 0, l = arguments.length; i < l; i++) {
if (arguments[i] !== this.args[i]) {
return false;
}
}

return true;
}

function calledWithExactly () {
return arguments.length == this.args.length &&
this.calledWith.apply(this, arguments);
}

function returned (value) {
return this.returnValue === value;
}

function threw (error) {
if (typeof error == "undefined" || !this.exception) {
return !!this.exception;
}

if (typeof error == "string") {
return this.exception.name == error;
}

return this.exception === error;
}

function create (thisObj, args, returnValue) {
var proxyCall = Object.create(spyCall);
delete proxyCall.create;
proxyCall.thisObj = thisObj;
proxyCall.args = args;
proxyCall.returnValue = returnValue;

return proxyCall;
}

return {
create: create,
calledOn: calledOn,
calledWith: calledWith,
calledWithExactly: calledWithExactly,
returned: returned,
threw: threw
};
}());

if (typeof sinon != "undefined") {
sinon.spy = spy;
sinon.spyCall = spyCall;
}
}());
77 changes: 77 additions & 0 deletions src/stub.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
(function () {
function stub (object, property, func) {
if (!object) {
throw new TypeError("Should stub property of object");
}

if (!!func && typeof func != "function") {
throw new TypeError("Custom stub should be function");
}

var method = object[property];
var type = typeof method;

if (!!method && type != "function") {
throw new TypeError("Attempted to stub " + type + " as function");
}

if (typeof func == "function") {
object[property] = !!sinon.spy ? sinon.spy(func) : func;
} else {
object[property] = stub.create();
}

object[property].restore = function () {
object[property] = method;
};

return object[property];
}

Object.extend(stub, (function () {
function create () {
function functionStub () {
if (functionStub.exception) {
throw functionStub.exception;
}

return functionStub.returnValue;
}

Object.extend(functionStub, stub);

if (sinon.spy) {
Object.extend(functionStub, sinon.spy);
}

return functionStub;
}

function returns (value) {
this.returnValue = value;

return this;
}

function throws (error, message) {
if (typeof error == "string") {
this.exception = new Error(message);
this.exception.name = error;
} else {
this.exception = error;
}

return this;
}

return {
create: create,
returns: returns,
throws: throws
};
}()));

if (typeof sinon == "object") {
sinon.stub = stub;
}
}());
5 changes: 5 additions & 0 deletions test/sinon_test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
TestCase("SinonTest", {
"test sinon should be object": function () {
assertObject(sinon);
}
});
Loading

0 comments on commit 0feec9f

Please sign in to comment.