jsdoc/test/lib/jsmock.js
Michael Mathews a2bb5358e6 Squashed 'lib/Rhino-Require/' content from commit b34307a
git-subtree-dir: lib/Rhino-Require
git-subtree-split: b34307ac1534b153df376820a115efa3be76b092
2011-05-07 15:14:34 +01:00

378 lines
11 KiB
JavaScript

/*
* JSMock 1.2.2, a mock object library for JavaScript
* Copyright (C) 2006 Justin DeWind
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
JSMock = {
extend: function(object) {
var mockControl = new MockControl();
object.createMock = function(objectToMock) {return mockControl.createMock(objectToMock)};
object.resetMocks = function() {mockControl.reset()};
object.verifyMocks = function() {mockControl.verify()};
if(!object.tearDown) {
object.tearDown = function() {
object.verifyMocks();
}
}
else if(object.tearDown.constructor == Function) {
object.__oldTearDown__ = object.tearDown;
object.tearDown = function() {
object.__oldTearDown__();
object.verifyMocks();
}
}
}
}
function MockControl() {
this.__expectationMatcher = new ExpectationMatcher();
this.__lastMock = null;
this.__lastCallName = null;
}
MockControl.prototype = {
createMock: function(objectToMock) {
var mock = { calls: [], expects: function() {this.__recording = true; return this}, __recording: false};
mock.expect = mock.expects;
if(objectToMock != null) {
if( typeof(objectToMock) == 'function' ) {
this.__createMethods(objectToMock, mock);
this.__createMethods(new objectToMock(), mock);
}
else if( typeof(objectToMock) == 'object') {
this.__createMethods(objectToMock, mock);
}
else {
throw new Error("Cannot mock out a " + typeof(objectToMock));
}
}
var self = this;
mock.addMockMethod = function(method) { self.__createMethod(self, mock, method); }
return mock;
},
andReturn: function(returnValue) {
this.__verifyLastMockNotNull("Cannot set return value without an expectation");
this.__initializeReturnExpectationForMock();
this.__lastMock.calls[this.__lastCallName].push( function() { return returnValue; });
},
andThrow: function(throwMsg) {
this.__verifyLastMockNotNull("Cannot throw error without an expectation");
this.__initializeReturnExpectationForMock();
this.__lastMock.calls[this.__lastCallName].push( function() { throw new Error(throwMsg); });
},
andStub: function(block) {
this.__verifyLastMockNotNull("Cannot stub without an expectation");
if( typeof(block) != 'function') {
throw new Error("Stub must be a function");
}
this.__initializeReturnExpectationForMock();
this.__lastMock.calls[this.__lastCallName].push( function() { return block.apply(this, arguments); });
},
reset: function() {
this.__expectationMatcher.reset();
},
verify: function() {
if(!this.__expectationMatcher.matches())
{
discrepancy = this.__expectationMatcher.discrepancy();
message = discrepancy.message;
method = discrepancy.behavior.method
formattedArgs = ArgumentFormatter.format(discrepancy.behavior.methodArguments);
this.__expectationMatcher.reset();
throw new Error(message + ": " + method + "(" + formattedArgs + ")");
}
else {
this.__expectationMatcher.reset();
}
},
__createMethods: function(object, mock) {
for( property in object ) {
if( this.__isPublicMethod(object, property) ) {
this.__createMethod( this, mock, property );
}
}
},
__createMethod: function(control, mock, method) {
mock[method] =
function() {
if( mock.__recording ) {
control.__lastMock = mock;
control.__lastCallName = method;
control.__expectationMatcher.addExpectedMethodCall( mock, method, arguments );
mock.__recording = false;
return control;
}
else {
control.__expectationMatcher.addActualMethodCall( mock, method, arguments );
if( mock.calls[method] != null) {
returnValue = mock.calls[method].shift();
if( typeof(returnValue) == 'function') {
return returnValue.apply(this, arguments);
}
}
}
}
},
__isPublicMethod: function(object, property) {
try {
return typeof(object[property]) == 'function' && property.charAt(0) != "_";
} catch(e) {
return false;
}
},
__verifyLastMockNotNull: function(throwMsg) {
if(this.__lastMock == null) {
throw new Error(throwMsg);
}
},
__initializeReturnExpectationForMock: function() {
if(typeof(this.__lastMock.calls[this.__lastCallName]) == 'undefined') {
this.__lastMock.calls[this.__lastCallName] = [];
}
}
}
function ExpectationMatcher() {
this.__expectationBehaviorList = [];
this.__actualBehaviorList = [];
this.__discrepancy = null;
}
ExpectationMatcher.prototype = {
addExpectedMethodCall: function(caller, method, methodArguments ) {
this.__expectationBehaviorList.push(new InvocationBehavior(caller, method, methodArguments));
},
addActualMethodCall: function(caller, method, methodArguments ) {
this.__actualBehaviorList.push(new InvocationBehavior(caller, method, methodArguments));
},
matches: function() {
var self = this;
var matches = true;
this.__expectationBehaviorList.eachIndexForJsMock(function(index, expectedBehavior) {
var actualBehavior = (self.__actualBehaviorList.length > index) ? self.__actualBehaviorList[index] : null;
if(matches) {
if( actualBehavior === null ) {
self.__discrepancy = new Discrepancy("Expected function not called", expectedBehavior);
matches = false;
}
else if( expectedBehavior.method != actualBehavior.method ) {
self.__discrepancy = new Discrepancy("Surprise call", actualBehavior);
matches = false;
}
else if( expectedBehavior.caller != actualBehavior.caller ) {
self.__discrepancy = new Discrepancy("Surprise call from unexpected caller", actualBehavior);
matches = false;
}
else if( !self.__matchArguments(expectedBehavior.methodArguments, actualBehavior.methodArguments) ) {
self.__discrepancy = new Discrepancy("Unexpected Arguments", actualBehavior);
matches = false;
}
}
});
if( this.__actualBehaviorList.length > this.__expectationBehaviorList.length && matches ) {
this.__discrepancy = new Discrepancy("Surprise call", this.__actualBehaviorList[this.__expectationBehaviorList.length]);
matches = false
}
return matches;
},
reset: function() {
this.__expectationBehaviorList = [];
this.__actualBehaviorList = [];
this.__discrepancy = null;
},
discrepancy: function() {
return this.__discrepancy;
},
__matchArguments: function(expectedArgs, actualArgs) {
var expectedArray = this.__convertArgumentsToArray(expectedArgs);
var actualArray = this.__convertArgumentsToArray(actualArgs);
return ArgumentMatcher.matches(expectedArray, actualArray);
},
__convertArgumentsToArray: function(args) {
var convertedArguments = [];
for(var i = 0; i < args.length; i++) {
convertedArguments[i] = args[i];
}
return convertedArguments;
}
}
function InvocationBehavior(caller, method, methodArguments) {
this.caller = caller;
this.method = method;
this.methodArguments = methodArguments;
}
function TypeOf(type) {
if(typeof(type) != 'function')
throw new Error("Can only take constructors");
this.type = type;
}
TypeOf.isA = function(type) { return new TypeOf(type); };
ArgumentMatcher = {
matches: function(expected, actual) {
return this.__delegateMatching(expected, actual);
},
__delegateMatching: function(expected, actual) {
if( expected == null ) {
return this.__match( expected, actual );
}
else if( expected.constructor == TypeOf ) {
return this.__match(expected.type, actual.constructor);
}
else if( expected.constructor == Array ) {
return this.__matchArrays(expected, actual);
}
else {
return this.__match(expected, actual);
}
},
__match: function(expected, actual) {
return ( expected == actual );
},
__matchArrays: function(expected, actual) {
if ( actual == null)
return false;
if( actual.constructor != Array)
return false;
if( expected.length != actual.length )
return false;
for(var i = 0; i < expected.length; i++ ) {
if( !this.__delegateMatching(expected[i], actual[i]) )
return false;
}
return true;
}
}
function Discrepancy(message, behavior) {
if(behavior.constructor != InvocationBehavior)
throw new Error("The behavior can only be an InvocationBehavior object");
this.message = message;
this.behavior = behavior;
}
ArgumentFormatter = {
format: function(args) {
var formattedArgs = "";
for(var i = 0; i < args.length; i++) {
if( args[i] == null ) {
formattedArgs += ( formattedArgs == "" ) ? "null" : ", " + "null";
}
else if( args[i].constructor == TypeOf || args[i].constructor == Function) {
var func = ( args[i].constructor == TypeOf ) ? args[i].type : args[i];
formattedArgs += ( formattedArgs == "" ) ? this.__formatFunction(func) : ", " + this.__formatFunction(func);
}
else if( typeof(args[i]) == "string" ) {
formattedArgs += ( formattedArgs == "" ) ? "\"" + args[i].toString() + "\"" : ", \"" + args[i].toString() + "\""
}
else if( args[i].constructor == Array ) {
formattedArgs += ( formattedArgs == "" ) ? "[" + this.format(args[i]) + "]" : ", [" + this.format(args[i]) + "]";
}
else {
formattedArgs += ( formattedArgs == "" ) ? args[i].toString() : ", " + args[i].toString();
}
}
return formattedArgs;
},
__formatFunction: function(func) {
// Manual checking is done for internal/native functions
// since Safari will not display them correctly
// for the intended regex parsing.
if(func == Array) {
return "Array";
} else if(func == Date) {
return "Date";
} else if(func == Object) {
return "Object";
} else if(func == String) {
return "String";
} else if(func == Function) {
return "Function";
} else if(func == RegExp) {
return "RegExp";
} else if(func == Error) {
return "Error";
} else if(func == Number) {
return "Number";
} else if(func == Boolean) {
return "Boolean";
}
var formattedFunc = func.toString().match(/function (\w+)/);
return ( formattedFunc == null ) ? "{{Closure}}" : formattedFunc[1];
}
}
/* Helpers */
// Implemented each method with a unique name to avoid conflicting
// with other libraries that implement it.
Array.prototype.eachIndexForJsMock = function(block) {
for(var index = 0; index < this.length; index++)
{
block(index, this[index]);
}
}