Permalink
Browse files

Merge branch 'kristopher'

  • Loading branch information...
Larry Karnowski
Larry Karnowski committed Jul 11, 2009
2 parents 9cee1c2 + 66958db commit be78e2d8cea4c69c8e465d038659a2d96053003f
Showing with 225 additions and 53 deletions.
  1. +43 −0 lib/smoke.core.js
  2. +46 −44 lib/smoke.mock.js
  3. +115 −0 spec/core_spec.js
  4. +20 −9 spec/mock_spec.js
  5. +1 −0 spec/suite.html
View
@@ -2,5 +2,48 @@ Smoke = {
print: function(v) {
// use the jquery print plugin if it is available or fall back to toString();
return (jQuery && jQuery.print) ? jQuery.print(v) : v.toString();
+ },
+
+ printArguments: function(args) {
+ var a = [];
+ if (args === undefined) args = '';
+ if ((args && args.callee) || (args instanceof Array)) {
+ for(var i = 0; i < args.length; i++) {
+ a.push(Smoke.print(args[i]));
+ }
+ } else {
+ // Workaround for jQuery.print returning "null" when called with an empty string.
+ if (!args && (typeof args == 'string')) {
+ a.push('');
+ } else {
+ a.push(Smoke.print(args));
+ }
+ }
+ return '(' + a.join(', ') + ')';
+ },
+
+ argumentsToArray: function(args) {
+ return Array.prototype.slice.call(args);
+ },
+
+ compare: function(a, b) {
+ if (a === b) return true;
+ if (a instanceof Array) {
+ if (b.length != a.length) return false;
+ for (var i = 0; i < b.length; i++)
+ if (!this.compare(a[i], b[i])) return false;
+ } else if (a instanceof Object) {
+ for (var key in a)
+ if (!this.compare(a[key], b[key])) return false;
+ for (var key in b)
+ if (!this.compare(b[key], a[key])) return false;
+ } else {
+ return false;
+ }
+ return true;
+ },
+
+ compareArguments: function(a, b) {
+ return this.compare(Smoke.argumentsToArray(a), Smoke.argumentsToArray(b));
}
};
View
@@ -29,22 +29,12 @@ Smoke.Mock = function(originalObj) {
};
obj.should_receive = function(attr){
- var expectation = new Smoke.Mock.Expectation(this, attr);
- if(this._expectations[attr]==undefined) this._expectations[attr] = [];
- this._expectations[attr].push(expectation);
- var previousFunction = this[attr];
- var mock = this;
- if(this._expectations[attr].length>1) return expectation;
- this[attr] = function() {
- var ret = undefined;
- for(var i=0; i<mock._expectations[attr].length; i++) {
- var expectation = mock._expectations[attr][i];
- var ran = expectation.run(arguments);
- if(ran && expectation.hasReturnValue) ret = expectation.returnValue;
- };
- return ret;
- };
- return expectation;
+ var expectation = new Smoke.Mock.Expectation(this, attr)
+ this._expectations[attr] = (this._expectations[attr] || []).concat([expectation]);
+ if(this._expectations[attr].length == 1) {
+ this[attr] = Smoke.Mock.Expectation.stub(this, attr);
+ }
+ return expectation;
};
obj.checkExpectations = function(){
@@ -59,13 +49,18 @@ Smoke.Mock = function(originalObj) {
};
Smoke.MockFunction = function(originalFunction, name) {
- var name = name || 'anonymous_function';
- var originalFunction = originalFunction || function() {};
- var scope = function() { return scope.mockFunction.apply(this,arguments) };
- scope[name] = originalFunction;
- scope.mockFunction = function() { return scope[name].apply(this,arguments); };
- var mock = Smoke.Mock(scope);
- mock.should_be_invoked = function() { return mock.should_receive(name) };
+ name = name || 'anonymous_function';
+ var mock = Smoke.Mock(function() {
+ var return_value = arguments.callee[name].apply(this, arguments);
+ if (return_value === undefined) {
+ return_value = (originalFunction || new Function()).apply(this, arguments)
+ }
+ return return_value;
+ });
+ mock[name] = (originalFunction || new Function());
+ mock.should_be_invoked = function() {
+ return this.should_receive(name);
+ }
return mock;
};
@@ -78,6 +73,22 @@ Smoke.Mock.Expectation = function(mock, attr) {
this.hasReturnValue = false;
};
+Smoke.Mock.Expectation.stub = function(mock, attr) {
+ return function() {
+ return function() {
+ var matched, return_value, args = arguments;
+ jQuery.each(this, function() {
+ this.run(args) && (matched = true) && (return_value = this.returnValue);
+ });
+ if (!matched) {
+ this[0].argumentMismatchError(args)
+ }
+ return return_value;
+ }.apply(mock._expectations[attr], arguments);
+ }
+}
+
+
Smoke.Mock.Expectation.prototype = {
exactly: function(count,type){
// type isn't used for now, it's just syntax ;)
@@ -98,9 +109,8 @@ Smoke.Mock.Expectation.prototype = {
return this
},
run: function(args){
- if(typeof(this.callerArgs) == 'undefined' || this.compareArrays(args, this.callerArgs)) {
- this.callCount+=1;
- return true;
+ if((this.callerArgs === undefined) || Smoke.compareArguments(args, this.callerArgs)) {
+ return !!(this.callCount+=1);
};
return false
},
@@ -125,28 +135,20 @@ Smoke.Mock.Expectation.prototype = {
if(this.maxCount>=this.callCount) Smoke.passed(this);//console.log('Mock passed!')
else Smoke.failed(this, 'expected '+this.methodSignature()+' to be called at most '+this.maxCount+" times but it actually got called "+this.callCount+' times');
},
+ argumentMismatchError: function(args) {
+ Smoke.failed(this, 'expected ' + this._attr + ' with ' + Smoke.printArguments(this.callerArgs) + ' but received it with ' + Smoke.printArguments(args));
+ },
methodSignature: function(){
- var a = '';
- var args = this.callerArgs || [];
- for(var i=0; i<args.length; i++) a += Smoke.print(args[i])+', ';
- a =a.slice(0,-2);
- return this._attr+'('+a+')'
+ return this._attr + Smoke.printArguments(this.callerArgs);
},
parseCount: function(c){
switch(c){
- case 'once' : c=1; break;
- case 'twice' : c=2; break;
+ case 'once':
+ return 1;
+ case 'twice':
+ return 2;
+ default:
+ return c;
}
- return c;
- },
- compareArrays: function(a,b) {
- if (a.length != b.length) return false;
- for (var i = 0; i < b.length; i++) {
- if (a[i].compare) {
- if (!a[i].compare(b[i])) return false;
- }
- if (a[i] !== b[i]) return false;
- }
- return true;
}
};
View
@@ -0,0 +1,115 @@
+Screw.Unit(function() {
+ describe("core", function() {
+ var anonymous_function = function() { return arguments };
+ describe("printArguments", function() {
+ it("should return '()' is the arguments are empty", function() {
+ expect(Smoke.printArguments(arguments)).to(equal, '()');
+ });
+
+ it("should return '()' is the arguments are undefined", function() {
+ expect(Smoke.printArguments()).to(equal, '()');
+ });
+
+ it("should return the arguments comma seperated wrapped in parenthesis", function() {
+ var args = anonymous_function(1,2);
+ expect(Smoke.printArguments(args)).to(equal, '(1, 2)');
+ });
+
+ it("should handle being passed something other than an array or arguments object", function() {
+ expect(Smoke.printArguments(false)).to(equal, '(false)');
+ });
+ });
+
+ describe("argumentsToArray", function() {
+ it("should return an array", function() {
+ expect(Smoke.argumentsToArray(anonymous_function(1,2)) instanceof Array).to(equal, true);
+ });
+
+ it("should return the arguments in an array", function() {
+ expect(Smoke.argumentsToArray(anonymous_function(1,2))).to(equal, [1,2]);
+ });
+ });
+
+ describe("compare", function() {
+ describe("with arrays", function() {
+ var array = [1,2,3], nested_array = [[1,2], [3,4]];
+ it("should return true if the two arrays are equal", function() {
+ expect(Smoke.compare(array, [1,2,3])).to(equal, true);
+ });
+
+ it("should return true if the two nested arrays are equal", function() {
+ expect(Smoke.compare(nested_array, [[1,2], [3,4]])).to(equal, true);
+ });
+
+ it("should return false if the two arrays are not equal", function() {
+ expect(Smoke.compare(array, [1,2,3,4])).to(equal, false);
+ })
+
+ it("should return false if the two nested arrays are not equal", function() {
+ expect(Smoke.compare(nested_array, [[1,2],[3]])).to(equal, false);
+ })
+ });
+
+ describe("with objects", function() {
+ var object = { foo: 'bar' }, nested_object = { foo: { a: 'b' }, bar: { c: 'd'} };
+ it("should return true if the two objects are equal", function() {
+ expect(Smoke.compare(object, { foo: 'bar' })).to(equal, true);
+ });
+
+ it("should return true if the two nested objects are equal", function() {
+ expect(Smoke.compare(nested_object, { foo: { a: 'b' }, bar: { c: 'd'} })).to(equal, true);
+ });
+
+ it("should return false if the two objects are not equal", function() {
+ expect(Smoke.compare(object, {bar: 'foo'})).to(equal, false);
+ });
+
+ it("should return false if the two nested objects are not equal", function() {
+ expect(Smoke.compare(nested_object, { foo: { c: 'd' }, bar: { a: 'b' } })).to(equal, false);
+ });
+
+ it("should return false if an one of the objects has an additional property", function() {
+ expect(Smoke.compare(object, { foo: 'bar', bar: 'foo' })).to(equal, false);
+ })
+ });
+
+ describe('with value types', function() {
+ var string = 'foo', number = 1;
+ it("should return true if the two strings are equal", function() {
+ expect(Smoke.compare(string, 'foo')).to(equal, true);
+ });
+
+ it("should return true if the two numbers are equal", function() {
+ expect(Smoke.compare(number, 1)).to(equal, true);
+ });
+
+ it("should return false if the two strings are not equal", function() {
+ expect(Smoke.compare(string, 'bar')).to(equal, false);
+ });
+
+ it("should return false if the two number are not equal", function() {
+ expect(Smoke.compare(number, 2)).to(equal, false);
+ });
+ })
+
+ describe("with mixed types", function() {
+ var array = [1, { foo: 'bar'}, '2'], object = { foo: [1,2,3], bar: 'foo', one: 1 };
+ it("should return true if the two arrays with mixed types are equal", function() {
+ expect(Smoke.compare(array, [1, { foo: 'bar'}, '2'])).to(equal, true);
+ });
+
+ it("should return false if the two arrays with mixed types are not equal", function() {
+ expect(Smoke.compare(array, [1, { foo: 'bar'}, 3])).to(equal, false);
+ });
+
+ it("should return true if the two objects with mixed types are equal", function() {
+ expect(Smoke.compare(object, { foo: [1,2,3], bar: 'foo', one: 1 })).to(equal, true);
+ });
+
+ it("should return false if the two objects with mixed types are not equal", function() {
+ expect(Smoke.compare(object, { foo: [1,2,3], bar: 'foo', two: 3 })).to(equal, false);
+ });
+ })
+ });
+ });
+});
View
@@ -72,10 +72,15 @@ Screw.Unit(function() {
mockObj.should_receive('foo').with_arguments('bar',baz).and_return('foobar');
expect(mockObj.foo('bar',baz)).to(equal, 'foobar');
});
- it("should return undefined if the arguments aren't matched", function() {
+
+ it("should throw and arguments mismatched error if the arguments aren't matched", function() {
mockObj = mock()
mockObj.should_receive('foo').with_arguments('bar').and_return('foobar');
- expect(mockObj.foo('chicken')).to(equal, undefined);
+ try {
+ mockObj.foo('chicken');
+ } catch(e) {
+ expect(e).to(equal, 'expected foo with ("bar") but received it with ("chicken")')
+ }
});
it("should allow mocking multiple method signatures with different returns", function() {
mockObj = mock()
@@ -89,12 +94,6 @@ Screw.Unit(function() {
mockObj.should_receive('foo').with_arguments('bar').exactly('once');
mockObj.foo('bar')
});
- it("should only mock the exact method signature when with_arguments is used with no arguments", function() {
- mockObj = mock();
- mockObj.should_receive('foo').with_arguments().exactly('once');
- mockObj.foo('should ignore this call');
- mockObj.foo();
- });
});
describe("added ontop of an existing object", function() {
@@ -164,6 +163,18 @@ Screw.Unit(function() {
mockObj('a');
mockObj('a');
});
+
+ it("should allow a return value to be set", function() {
+ mockObj.should_be_invoked().and_return('bar');
+ expect(mockObj('foo')).to(equal, 'bar');
+ });
+
+ it("should allow multiple return values to be set through the argument matchers", function() {
+ mockObj.should_be_invoked().with_arguments('foo').and_return('bar');
+ mockObj.should_be_invoked().with_arguments('bar').and_return('foo');
+ expect(mockObj('foo')).to(equal, 'bar');
+ expect(mockObj('bar')).to(equal, 'foo');
+ });
it("allows passing in a name for the function as a second argument to make error messages clearer", function() {
mock_function(foo, 'foo').should_be_invoked().exactly('once');
@@ -208,6 +219,6 @@ Screw.Unit(function() {
(new Aobj()).aFunction();
(new Aobj()).aFunction();
});
- });
+ });
});
});
View
@@ -20,6 +20,7 @@
<!-- load specs -->
<script src="stub_spec.js"></script>
<script src="mock_spec.js"></script>
+ <script src="core_spec.js"></script>
<script src="screw_integration_spec.js"></script>
<link rel="stylesheet" href="su/screw.css">

0 comments on commit be78e2d

Please sign in to comment.