3 * http://mock4js.sourceforge.net/
\r
8 _convertToConstraint: function(constraintOrValue) {
\r
9 if(constraintOrValue.argumentMatches) {
\r
10 return constraintOrValue; // it's already an ArgumentMatcher
\r
12 return new MatchExactly(constraintOrValue); // default to eq(...)
\r
15 addMockSupport: function(object) {
\r
17 object.mock = function(mockedType) {
\r
19 throw new Mock4JSException("Cannot create mock: type to mock cannot be found or is null");
\r
21 var newMock = new Mock(mockedType);
\r
22 Mock4JS._mocksToVerify.push(newMock);
\r
26 // syntactic sugar for expects()
\r
27 object.once = function() {
\r
28 return new CallCounter(1);
\r
30 object.never = function() {
\r
31 return new CallCounter(0);
\r
33 object.exactly = function(expectedCallCount) {
\r
34 return new CallCounter(expectedCallCount);
\r
36 object.atLeastOnce = function() {
\r
37 return new InvokeAtLeastOnce();
\r
40 // syntactic sugar for argument expectations
\r
41 object.ANYTHING = new MatchAnything();
\r
42 object.NOT_NULL = new MatchAnythingBut(new MatchExactly(null));
\r
43 object.NOT_UNDEFINED = new MatchAnythingBut(new MatchExactly(undefined));
\r
44 object.eq = function(expectedValue) {
\r
45 return new MatchExactly(expectedValue);
\r
47 object.not = function(valueNotExpected) {
\r
48 var argConstraint = Mock4JS._convertToConstraint(valueNotExpected);
\r
49 return new MatchAnythingBut(argConstraint);
\r
51 object.and = function() {
\r
52 var constraints = [];
\r
53 for(var i=0; i<arguments.length; i++) {
\r
54 constraints[i] = Mock4JS._convertToConstraint(arguments[i]);
\r
56 return new MatchAllOf(constraints);
\r
58 object.or = function() {
\r
59 var constraints = [];
\r
60 for(var i=0; i<arguments.length; i++) {
\r
61 constraints[i] = Mock4JS._convertToConstraint(arguments[i]);
\r
63 return new MatchAnyOf(constraints);
\r
65 object.stringContains = function(substring) {
\r
66 return new MatchStringContaining(substring);
\r
69 // syntactic sugar for will()
\r
70 object.returnValue = function(value) {
\r
71 return new ReturnValueAction(value);
\r
73 object.throwException = function(exception) {
\r
74 return new ThrowExceptionAction(exception);
\r
77 clearMocksToVerify: function() {
\r
78 Mock4JS._mocksToVerify = [];
\r
80 verifyAllMocks: function() {
\r
81 for(var i=0; i<Mock4JS._mocksToVerify.length; i++) {
\r
82 Mock4JS._mocksToVerify[i].verify();
\r
88 hasFunction: function(obj, methodName) {
\r
89 return typeof obj == 'object' && typeof obj[methodName] == 'function';
\r
91 join: function(list) {
\r
93 for(var i=0; i<list.length; i++) {
\r
95 if(Mock4JSUtil.hasFunction(item, "describe")) {
\r
96 result += item.describe();
\r
98 else if(typeof list[i] == 'string') {
\r
99 result += "\""+list[i]+"\"";
\r
104 if(i<list.length-1) result += ", ";
\r
110 Mock4JSException = function(message) {
\r
111 this.message = message;
\r
114 Mock4JSException.prototype = {
\r
115 toString: function() {
\r
116 return this.message;
\r
121 * Assert function that makes use of the constraint methods
\r
123 assertThat = function(expected, argumentMatcher) {
\r
124 if(!argumentMatcher.argumentMatches(expected)) {
\r
125 fail("Expected '"+expected+"' to be "+argumentMatcher.describe());
\r
132 function CallCounter(expectedCount) {
\r
133 this._expectedCallCount = expectedCount;
\r
134 this._actualCallCount = 0;
\r
137 CallCounter.prototype = {
\r
138 addActualCall: function() {
\r
139 this._actualCallCount++;
\r
140 if(this._actualCallCount > this._expectedCallCount) {
\r
141 throw new Mock4JSException("unexpected invocation");
\r
145 verify: function() {
\r
146 if(this._actualCallCount < this._expectedCallCount) {
\r
147 throw new Mock4JSException("expected method was not invoked the expected number of times");
\r
151 describe: function() {
\r
152 if(this._expectedCallCount == 0) {
\r
153 return "not expected";
\r
154 } else if(this._expectedCallCount == 1) {
\r
155 var msg = "expected once";
\r
156 if(this._actualCallCount >= 1) {
\r
157 msg += " and has been invoked";
\r
161 var msg = "expected "+this._expectedCallCount+" times";
\r
162 if(this._actualCallCount > 0) {
\r
163 msg += ", invoked "+this._actualCallCount + " times";
\r
170 function InvokeAtLeastOnce() {
\r
171 this._hasBeenInvoked = false;
\r
174 InvokeAtLeastOnce.prototype = {
\r
175 addActualCall: function() {
\r
176 this._hasBeenInvoked = true;
\r
179 verify: function() {
\r
180 if(this._hasBeenInvoked === false) {
\r
181 throw new Mock4JSException(describe());
\r
185 describe: function() {
\r
186 var desc = "expected at least once";
\r
187 if(this._hasBeenInvoked) desc+=" and has been invoked";
\r
196 function MatchExactly(expectedValue) {
\r
197 this._expectedValue = expectedValue;
\r
200 MatchExactly.prototype = {
\r
201 argumentMatches: function(actualArgument) {
\r
202 if(this._expectedValue instanceof Array) {
\r
203 if(!(actualArgument instanceof Array)) return false;
\r
204 if(this._expectedValue.length != actualArgument.length) return false;
\r
205 for(var i=0; i<this._expectedValue.length; i++) {
\r
206 if(this._expectedValue[i] != actualArgument[i]) return false;
\r
210 return this._expectedValue == actualArgument;
\r
213 describe: function() {
\r
214 if(typeof this._expectedValue == "string") {
\r
215 return "eq(\""+this._expectedValue+"\")";
\r
217 return "eq("+this._expectedValue+")";
\r
222 function MatchAnything() {
\r
225 MatchAnything.prototype = {
\r
226 argumentMatches: function(actualArgument) {
\r
229 describe: function() {
\r
234 function MatchAnythingBut(matcherToNotMatch) {
\r
235 this._matcherToNotMatch = matcherToNotMatch;
\r
238 MatchAnythingBut.prototype = {
\r
239 argumentMatches: function(actualArgument) {
\r
240 return !this._matcherToNotMatch.argumentMatches(actualArgument);
\r
242 describe: function() {
\r
243 return "not("+this._matcherToNotMatch.describe()+")";
\r
247 function MatchAllOf(constraints) {
\r
248 this._constraints = constraints;
\r
252 MatchAllOf.prototype = {
\r
253 argumentMatches: function(actualArgument) {
\r
254 for(var i=0; i<this._constraints.length; i++) {
\r
255 var constraint = this._constraints[i];
\r
256 if(!constraint.argumentMatches(actualArgument)) return false;
\r
260 describe: function() {
\r
261 return "and("+Mock4JSUtil.join(this._constraints)+")";
\r
265 function MatchAnyOf(constraints) {
\r
266 this._constraints = constraints;
\r
269 MatchAnyOf.prototype = {
\r
270 argumentMatches: function(actualArgument) {
\r
271 for(var i=0; i<this._constraints.length; i++) {
\r
272 var constraint = this._constraints[i];
\r
273 if(constraint.argumentMatches(actualArgument)) return true;
\r
277 describe: function() {
\r
278 return "or("+Mock4JSUtil.join(this._constraints)+")";
\r
283 function MatchStringContaining(stringToLookFor) {
\r
284 this._stringToLookFor = stringToLookFor;
\r
287 MatchStringContaining.prototype = {
\r
288 argumentMatches: function(actualArgument) {
\r
289 if(typeof actualArgument != 'string') throw new Mock4JSException("stringContains() must be given a string, actually got a "+(typeof actualArgument));
\r
290 return (actualArgument.indexOf(this._stringToLookFor) != -1);
\r
292 describe: function() {
\r
293 return "a string containing \""+this._stringToLookFor+"\"";
\r
301 function StubInvocation(expectedMethodName, expectedArgs, actionSequence) {
\r
302 this._expectedMethodName = expectedMethodName;
\r
303 this._expectedArgs = expectedArgs;
\r
304 this._actionSequence = actionSequence;
\r
307 StubInvocation.prototype = {
\r
308 matches: function(invokedMethodName, invokedMethodArgs) {
\r
309 if (invokedMethodName != this._expectedMethodName) {
\r
313 if (invokedMethodArgs.length != this._expectedArgs.length) {
\r
317 for(var i=0; i<invokedMethodArgs.length; i++) {
\r
318 var expectedArg = this._expectedArgs[i];
\r
319 var invokedArg = invokedMethodArgs[i];
\r
320 if(!expectedArg.argumentMatches(invokedArg)) {
\r
328 invoked: function() {
\r
330 return this._actionSequence.invokeNextAction();
\r
332 if(e instanceof Mock4JSException) {
\r
333 throw new Mock4JSException(this.describeInvocationNameAndArgs()+" - "+e.message);
\r
341 this._actionSequence.addAll.apply(this._actionSequence, arguments);
\r
344 describeInvocationNameAndArgs: function() {
\r
345 return this._expectedMethodName+"("+Mock4JSUtil.join(this._expectedArgs)+")";
\r
348 describe: function() {
\r
349 return "stub: "+this.describeInvocationNameAndArgs();
\r
352 verify: function() {
\r
357 * ExpectedInvocation
\r
359 function ExpectedInvocation(expectedMethodName, expectedArgs, expectedCallCounter) {
\r
360 this._stubInvocation = new StubInvocation(expectedMethodName, expectedArgs, new ActionSequence());
\r
361 this._expectedCallCounter = expectedCallCounter;
\r
364 ExpectedInvocation.prototype = {
\r
365 matches: function(invokedMethodName, invokedMethodArgs) {
\r
367 return this._stubInvocation.matches(invokedMethodName, invokedMethodArgs);
\r
369 throw new Mock4JSException("method "+this._stubInvocation.describeInvocationNameAndArgs()+": "+e.message);
\r
373 invoked: function() {
\r
375 this._expectedCallCounter.addActualCall();
\r
377 throw new Mock4JSException(e.message+": "+this._stubInvocation.describeInvocationNameAndArgs());
\r
379 return this._stubInvocation.invoked();
\r
383 this._stubInvocation.will.apply(this._stubInvocation, arguments);
\r
386 describe: function() {
\r
387 return this._expectedCallCounter.describe()+": "+this._stubInvocation.describeInvocationNameAndArgs();
\r
390 verify: function() {
\r
392 this._expectedCallCounter.verify();
\r
394 throw new Mock4JSException(e.message+": "+this._stubInvocation.describeInvocationNameAndArgs());
\r
402 function ReturnValueAction(valueToReturn) {
\r
403 this._valueToReturn = valueToReturn;
\r
406 ReturnValueAction.prototype = {
\r
407 invoke: function() {
\r
408 return this._valueToReturn;
\r
410 describe: function() {
\r
411 return "returns "+this._valueToReturn;
\r
415 function ThrowExceptionAction(exceptionToThrow) {
\r
416 this._exceptionToThrow = exceptionToThrow;
\r
419 ThrowExceptionAction.prototype = {
\r
420 invoke: function() {
\r
421 throw this._exceptionToThrow;
\r
423 describe: function() {
\r
424 return "throws "+this._exceptionToThrow;
\r
428 function ActionSequence() {
\r
429 this._ACTIONS_NOT_SETUP = "_ACTIONS_NOT_SETUP";
\r
430 this._actionSequence = this._ACTIONS_NOT_SETUP;
\r
431 this._indexOfNextAction = 0;
\r
434 ActionSequence.prototype = {
\r
435 invokeNextAction: function() {
\r
436 if(this._actionSequence === this._ACTIONS_NOT_SETUP) {
\r
439 if(this._indexOfNextAction >= this._actionSequence.length) {
\r
440 throw new Mock4JSException("no more values to return");
\r
442 var action = this._actionSequence[this._indexOfNextAction];
\r
443 this._indexOfNextAction++;
\r
444 return action.invoke();
\r
449 addAll: function() {
\r
450 this._actionSequence = [];
\r
451 for(var i=0; i<arguments.length; i++) {
\r
452 if(typeof arguments[i] != 'object' && arguments[i].invoke === undefined) {
\r
453 throw new Error("cannot add a method action that does not have an invoke() method");
\r
455 this._actionSequence.push(arguments[i]);
\r
460 function StubActionSequence() {
\r
461 this._ACTIONS_NOT_SETUP = "_ACTIONS_NOT_SETUP";
\r
462 this._actionSequence = this._ACTIONS_NOT_SETUP;
\r
463 this._indexOfNextAction = 0;
\r
466 StubActionSequence.prototype = {
\r
467 invokeNextAction: function() {
\r
468 if(this._actionSequence === this._ACTIONS_NOT_SETUP) {
\r
470 } else if(this._actionSequence.length == 1) {
\r
471 // if there is only one method action, keep doing that on every invocation
\r
472 return this._actionSequence[0].invoke();
\r
474 if(this._indexOfNextAction >= this._actionSequence.length) {
\r
475 throw new Mock4JSException("no more values to return");
\r
477 var action = this._actionSequence[this._indexOfNextAction];
\r
478 this._indexOfNextAction++;
\r
479 return action.invoke();
\r
484 addAll: function() {
\r
485 this._actionSequence = [];
\r
486 for(var i=0; i<arguments.length; i++) {
\r
487 if(typeof arguments[i] != 'object' && arguments[i].invoke === undefined) {
\r
488 throw new Error("cannot add a method action that does not have an invoke() method");
\r
490 this._actionSequence.push(arguments[i]);
\r
499 function Mock(mockedType) {
\r
500 if(mockedType === undefined || mockedType.prototype === undefined) {
\r
501 throw new Mock4JSException("Unable to create Mock: must create Mock using a class not prototype, eg. 'new Mock(TypeToMock)' or using the convenience method 'mock(TypeToMock)'");
\r
503 this._mockedType = mockedType.prototype;
\r
504 this._expectedCallCount;
\r
505 this._isRecordingExpectations = false;
\r
506 this._expectedInvocations = [];
\r
509 var IntermediateClass = new Function();
\r
510 IntermediateClass.prototype = mockedType.prototype;
\r
511 var ChildClass = new Function();
\r
512 ChildClass.prototype = new IntermediateClass();
\r
513 this._proxy = new ChildClass();
\r
514 this._proxy.mock = this;
\r
516 for(property in mockedType.prototype) {
\r
517 if(this._isPublicMethod(mockedType.prototype, property)) {
\r
518 var publicMethodName = property;
\r
519 this._proxy[publicMethodName] = this._createMockedMethod(publicMethodName);
\r
520 this[publicMethodName] = this._createExpectationRecordingMethod(publicMethodName);
\r
527 proxy: function() {
\r
528 return this._proxy;
\r
531 expects: function(expectedCallCount) {
\r
532 this._expectedCallCount = expectedCallCount;
\r
533 this._isRecordingExpectations = true;
\r
534 this._isRecordingStubs = false;
\r
538 stubs: function() {
\r
539 this._isRecordingExpectations = false;
\r
540 this._isRecordingStubs = true;
\r
544 verify: function() {
\r
545 for(var i=0; i<this._expectedInvocations.length; i++) {
\r
546 var expectedInvocation = this._expectedInvocations[i];
\r
548 expectedInvocation.verify();
\r
550 var failMsg = e.message+this._describeMockSetup();
\r
551 throw new Mock4JSException(failMsg);
\r
556 _isPublicMethod: function(mockedType, property) {
\r
558 var isMethod = typeof(mockedType[property]) == 'function';
\r
559 var isPublic = property.charAt(0) != "_";
\r
560 return isMethod && isPublic;
\r
566 _createExpectationRecordingMethod: function(methodName) {
\r
567 return function() {
\r
568 // ensure all arguments are instances of ArgumentMatcher
\r
569 var expectedArgs = [];
\r
570 for(var i=0; i<arguments.length; i++) {
\r
571 if(arguments[i] !== null && arguments[i] !== undefined && arguments[i].argumentMatches) {
\r
572 expectedArgs[i] = arguments[i];
\r
574 expectedArgs[i] = new MatchExactly(arguments[i]);
\r
578 // create stub or expected invocation
\r
579 var expectedInvocation;
\r
580 if(this._isRecordingExpectations) {
\r
581 expectedInvocation = new ExpectedInvocation(methodName, expectedArgs, this._expectedCallCount);
\r
583 expectedInvocation = new StubInvocation(methodName, expectedArgs, new StubActionSequence());
\r
586 this._expectedInvocations.push(expectedInvocation);
\r
588 this._isRecordingExpectations = false;
\r
589 this._isRecordingStubs = false;
\r
590 return expectedInvocation;
\r
594 _createMockedMethod: function(methodName) {
\r
595 return function() {
\r
596 // go through expectation list backwards to ensure later expectations override earlier ones
\r
597 for(var i=this.mock._expectedInvocations.length-1; i>=0; i--) {
\r
598 var expectedInvocation = this.mock._expectedInvocations[i];
\r
599 if(expectedInvocation.matches(methodName, arguments)) {
\r
601 return expectedInvocation.invoked();
\r
603 if(e instanceof Mock4JSException) {
\r
604 throw new Mock4JSException(e.message+this.mock._describeMockSetup());
\r
606 // the user setup the mock to throw a specific error, so don't modify the message
\r
612 var failMsg = "unexpected invocation: "+methodName+"("+Mock4JSUtil.join(arguments)+")"+this.mock._describeMockSetup();
\r
613 throw new Mock4JSException(failMsg);
\r
617 _describeMockSetup: function() {
\r
618 var msg = "\nAllowed:";
\r
619 for(var i=0; i<this._expectedInvocations.length; i++) {
\r
620 var expectedInvocation = this._expectedInvocations[i];
\r
621 msg += "\n" + expectedInvocation.describe();
\r