Add new certificateProvider extension API.
[chromium-blink-merge.git] / chrome / third_party / mock4js / mock4js.js
blob5cd02d518dff1c1202967fc12fe81dce4d282c97
1 /**
2  * Mock4JS 0.2
3  * http://mock4js.sourceforge.net/
4  */
6 Mock4JS = {
7         _mocksToVerify: [],
8         _convertToConstraint: function(constraintOrValue) {
9                 if(constraintOrValue.argumentMatches) {
10                         return constraintOrValue; // it's already an ArgumentMatcher
11                 } else {
12                         return new MatchExactly(constraintOrValue);     // default to eq(...)
13                 }
14         },
15         addMockSupport: function(object) {
16                 // mock creation
17                 object.mock = function(mockedType) {
18                         if(!mockedType) {
19                                 throw new Mock4JSException("Cannot create mock: type to mock cannot be found or is null");
20                         }
21                         var newMock = new Mock(mockedType);
22                         Mock4JS._mocksToVerify.push(newMock);
23                         return newMock;
24                 }
26                 // syntactic sugar for expects()
27                 object.once = function() {
28                         return new CallCounter(1);
29                 }
30                 object.never = function() {
31                         return new CallCounter(0);
32                 }
33                 object.exactly = function(expectedCallCount) {
34                         return new CallCounter(expectedCallCount);
35                 }
36                 object.atLeastOnce = function() {
37                         return new InvokeAtLeastOnce();
38                 }
39                 
40                 // syntactic sugar for argument expectations
41                 object.ANYTHING = new MatchAnything();
42                 object.NOT_NULL = new MatchAnythingBut(new MatchExactly(null));
43                 object.NOT_UNDEFINED = new MatchAnythingBut(new MatchExactly(undefined));
44                 object.eq = function(expectedValue) {
45                         return new MatchExactly(expectedValue);
46                 }
47                 object.not = function(valueNotExpected) {
48                         var argConstraint = Mock4JS._convertToConstraint(valueNotExpected);
49                         return new MatchAnythingBut(argConstraint);
50                 }
51                 object.and = function() {
52                         var constraints = [];
53                         for(var i=0; i<arguments.length; i++) {
54                                 constraints[i] = Mock4JS._convertToConstraint(arguments[i]);
55                         }
56                         return new MatchAllOf(constraints);
57                 }
58                 object.or = function() {
59                         var constraints = [];
60                         for(var i=0; i<arguments.length; i++) {
61                                 constraints[i] = Mock4JS._convertToConstraint(arguments[i]);
62                         }
63                         return new MatchAnyOf(constraints);
64                 }
65                 object.stringContains = function(substring) {
66                         return new MatchStringContaining(substring);
67                 }
68                 
69                 // syntactic sugar for will()
70                 object.returnValue = function(value) {
71                         return new ReturnValueAction(value);
72                 }
73                 object.throwException = function(exception) {
74                         return new ThrowExceptionAction(exception);
75                 }
76         },
77         clearMocksToVerify: function() {
78                 Mock4JS._mocksToVerify = [];
79         },
80         verifyAllMocks: function() {
81                 for(var i=0; i<Mock4JS._mocksToVerify.length; i++) {
82                         Mock4JS._mocksToVerify[i].verify();
83                 }
84         }
87 Mock4JSUtil = {
88         hasFunction: function(obj, methodName) {
89                 return typeof obj == 'object' && typeof obj[methodName] == 'function';
90         },
91         join: function(list) {
92                 var result = "";
93                 for(var i=0; i<list.length; i++) {
94                         var item = list[i];
95                         if(Mock4JSUtil.hasFunction(item, "describe")) {
96                                 result += item.describe();
97                         }
98                         else if(typeof list[i] == 'string') {
99                                 result += "\""+list[i]+"\"";
100                         } else {
101                                 result += list[i];
102                         }
103                         
104                         if(i<list.length-1) result += ", ";
105                 }
106                 return result;
107         }       
110 Mock4JSException = function(message) {
111         this.message = message;
114 Mock4JSException.prototype = {
115         toString: function() {
116                 return this.message;
117         }
121  * Assert function that makes use of the constraint methods
122  */ 
123 assertThat = function(expected, argumentMatcher) {
124         if(!argumentMatcher.argumentMatches(expected)) {
125                 throw new Mock4JSException("Expected '"+expected+"' to be "+argumentMatcher.describe());
126         }
130  * CallCounter
131  */
132 function CallCounter(expectedCount) {
133         this._expectedCallCount = expectedCount;
134         this._actualCallCount = 0;
137 CallCounter.prototype = {
138         addActualCall: function() {
139                 this._actualCallCount++;
140                 if(this._actualCallCount > this._expectedCallCount) {
141                         throw new Mock4JSException("unexpected invocation");
142                 }
143         },
144         
145         verify: function() {
146                 if(this._actualCallCount < this._expectedCallCount) {
147                         throw new Mock4JSException("expected method was not invoked the expected number of times");
148                 }
149         },
150         
151         describe: function() {
152                 if(this._expectedCallCount == 0) {
153                         return "not expected";
154                 } else if(this._expectedCallCount == 1) {
155                         var msg = "expected once";
156                         if(this._actualCallCount >= 1) {
157                                 msg += " and has been invoked";
158                         }
159                         return msg;
160                 } else {
161                         var msg = "expected "+this._expectedCallCount+" times";
162                         if(this._actualCallCount > 0) {
163                                 msg += ", invoked "+this._actualCallCount + " times";
164                         }
165                         return msg;
166                 }
167         }
170 function InvokeAtLeastOnce() {
171         this._hasBeenInvoked = false;
174 InvokeAtLeastOnce.prototype = {
175         addActualCall: function() {
176                 this._hasBeenInvoked = true;
177         },
178         
179         verify: function() {
180                 if(this._hasBeenInvoked === false) {
181                         throw new Mock4JSException(describe());
182                 }
183         },
184         
185         describe: function() {
186                 var desc = "expected at least once";
187                 if(this._hasBeenInvoked) desc+=" and has been invoked";
188                 return desc;
189         }
193  * ArgumentMatchers
194  */
196 function MatchExactly(expectedValue) {
197         this._expectedValue = expectedValue;
200 MatchExactly.prototype = {
201         argumentMatches: function(actualArgument) {
202                 if(this._expectedValue instanceof Array) {
203                         if(!(actualArgument instanceof Array)) return false;
204                         if(this._expectedValue.length != actualArgument.length) return false;
205                         for(var i=0; i<this._expectedValue.length; i++) {
206                                 if(this._expectedValue[i] != actualArgument[i]) return false;
207                         }
208                         return true;
209                 } else {
210                         return this._expectedValue == actualArgument;
211                 }
212         },
213         describe: function() {
214                 if(typeof this._expectedValue == "string") {
215                         return "eq(\""+this._expectedValue+"\")";
216                 } else {
217                         return "eq("+this._expectedValue+")";
218                 }
219         }
222 function MatchAnything() {
225 MatchAnything.prototype = {
226         argumentMatches: function(actualArgument) {
227                 return true;
228         },
229         describe: function() {
230                 return "ANYTHING";
231         }
234 function MatchAnythingBut(matcherToNotMatch) {
235         this._matcherToNotMatch = matcherToNotMatch;
238 MatchAnythingBut.prototype = {
239         argumentMatches: function(actualArgument) {
240                 return !this._matcherToNotMatch.argumentMatches(actualArgument);
241         },
242         describe: function() {
243                 return "not("+this._matcherToNotMatch.describe()+")";
244         }
247 function MatchAllOf(constraints) {
248         this._constraints = constraints;
252 MatchAllOf.prototype = {
253         argumentMatches: function(actualArgument) {
254                 for(var i=0; i<this._constraints.length; i++) {
255                         var constraint = this._constraints[i];
256                         if(!constraint.argumentMatches(actualArgument)) return false;
257                 }
258                 return true;
259         },
260         describe: function() {
261                 return "and("+Mock4JSUtil.join(this._constraints)+")";
262         }
265 function MatchAnyOf(constraints) {
266         this._constraints = constraints;
269 MatchAnyOf.prototype = {
270         argumentMatches: function(actualArgument) {
271                 for(var i=0; i<this._constraints.length; i++) {
272                         var constraint = this._constraints[i];
273                         if(constraint.argumentMatches(actualArgument)) return true;
274                 }
275                 return false;
276         },
277         describe: function() {
278                 return "or("+Mock4JSUtil.join(this._constraints)+")";
279         }
283 function MatchStringContaining(stringToLookFor) {
284         this._stringToLookFor = stringToLookFor;
287 MatchStringContaining.prototype = {
288         argumentMatches: function(actualArgument) {
289                 if(typeof actualArgument != 'string') throw new Mock4JSException("stringContains() must be given a string, actually got a "+(typeof actualArgument));
290                 return (actualArgument.indexOf(this._stringToLookFor) != -1);
291         },
292         describe: function() {
293                 return "a string containing \""+this._stringToLookFor+"\"";
294         }
299  * StubInvocation
300  */
301 function StubInvocation(expectedMethodName, expectedArgs, actionSequence) {
302         this._expectedMethodName = expectedMethodName;
303         this._expectedArgs = expectedArgs;
304         this._actionSequence = actionSequence;
307 StubInvocation.prototype = {
308         matches: function(invokedMethodName, invokedMethodArgs) {
309                 if (invokedMethodName != this._expectedMethodName) {
310                         return false;
311                 }
312                 
313                 if (invokedMethodArgs.length != this._expectedArgs.length) {
314                         return false;
315                 }
316                 
317                 for(var i=0; i<invokedMethodArgs.length; i++) {
318                         var expectedArg = this._expectedArgs[i];
319                         var invokedArg = invokedMethodArgs[i];
320                         if(!expectedArg.argumentMatches(invokedArg)) {
321                                 return false;
322                         }
323                 }
324                 
325                 return true;
326         },
327         
328         invoked: function() {
329                 try {
330                         return this._actionSequence.invokeNextAction();
331                 } catch(e) {
332                         if(e instanceof Mock4JSException) {
333                                 throw new Mock4JSException(this.describeInvocationNameAndArgs()+" - "+e.message);
334                         } else {
335                                 throw e;
336                         }
337                 }
338         },
339         
340         will: function() {
341                 this._actionSequence.addAll.apply(this._actionSequence, arguments);
342         },
343         
344         describeInvocationNameAndArgs: function() {
345                 return this._expectedMethodName+"("+Mock4JSUtil.join(this._expectedArgs)+")";
346         },
347         
348         describe: function() {
349                 return "stub: "+this.describeInvocationNameAndArgs();
350         },
351         
352         verify: function() {
353         }
357  * ExpectedInvocation
358  */
359 function ExpectedInvocation(expectedMethodName, expectedArgs, expectedCallCounter) {
360         this._stubInvocation = new StubInvocation(expectedMethodName, expectedArgs, new ActionSequence());
361         this._expectedCallCounter = expectedCallCounter;
364 ExpectedInvocation.prototype = {
365         matches: function(invokedMethodName, invokedMethodArgs) {
366                 try {
367                         return this._stubInvocation.matches(invokedMethodName, invokedMethodArgs);
368                 } catch(e) {
369                         throw new Mock4JSException("method "+this._stubInvocation.describeInvocationNameAndArgs()+": "+e.message);
370                 }
371         },
372         
373         invoked: function() {
374                 try {
375                         this._expectedCallCounter.addActualCall();
376                 } catch(e) {
377                         throw new Mock4JSException(e.message+": "+this._stubInvocation.describeInvocationNameAndArgs());
378                 }
379                 return this._stubInvocation.invoked();
380         },
381         
382         will: function() {
383                 this._stubInvocation.will.apply(this._stubInvocation, arguments);
384         },
385         
386         describe: function() {
387                 return this._expectedCallCounter.describe()+": "+this._stubInvocation.describeInvocationNameAndArgs();
388         },
389         
390         verify: function() {
391                 try {
392                         this._expectedCallCounter.verify();
393                 } catch(e) {
394                         throw new Mock4JSException(e.message+": "+this._stubInvocation.describeInvocationNameAndArgs());
395                 }
396         }
400  * MethodActions
401  */
402 function ReturnValueAction(valueToReturn) {
403         this._valueToReturn = valueToReturn;
406 ReturnValueAction.prototype = {
407         invoke: function() {
408                 return this._valueToReturn;
409         },
410         describe: function() {
411                 return "returns "+this._valueToReturn;
412         }
415 function ThrowExceptionAction(exceptionToThrow) {
416         this._exceptionToThrow = exceptionToThrow;
419 ThrowExceptionAction.prototype = {
420         invoke: function() {
421                 throw this._exceptionToThrow;
422         },
423         describe: function() {
424                 return "throws "+this._exceptionToThrow;
425         }
428 function ActionSequence() {
429         this._ACTIONS_NOT_SETUP = "_ACTIONS_NOT_SETUP";
430         this._actionSequence = this._ACTIONS_NOT_SETUP;
431         this._indexOfNextAction = 0;
434 ActionSequence.prototype = {
435         invokeNextAction: function() {
436                 if(this._actionSequence === this._ACTIONS_NOT_SETUP) {
437                         return;
438                 } else {
439                         if(this._indexOfNextAction >= this._actionSequence.length) {
440                                 throw new Mock4JSException("no more values to return");
441                         } else {
442                                 var action = this._actionSequence[this._indexOfNextAction];
443                                 this._indexOfNextAction++;
444                                 return action.invoke();
445                         }
446                 }
447         },
448         
449         addAll: function() {
450                 this._actionSequence = [];
451                 for(var i=0; i<arguments.length; i++) {
452                         if(typeof arguments[i] != 'object' && arguments[i].invoke === undefined) {
453                                 throw new Error("cannot add a method action that does not have an invoke() method");
454                         }
455                         this._actionSequence.push(arguments[i]);
456                 }
457         }
460 function StubActionSequence() {
461         this._ACTIONS_NOT_SETUP = "_ACTIONS_NOT_SETUP";
462         this._actionSequence = this._ACTIONS_NOT_SETUP;
463         this._indexOfNextAction = 0;
466 StubActionSequence.prototype = {
467         invokeNextAction: function() {
468                 if(this._actionSequence === this._ACTIONS_NOT_SETUP) {
469                         return;
470                 } else if(this._actionSequence.length == 1) {
471                         // if there is only one method action, keep doing that on every invocation
472                         return this._actionSequence[0].invoke();
473                 } else {
474                         if(this._indexOfNextAction >= this._actionSequence.length) {
475                                 throw new Mock4JSException("no more values to return");
476                         } else {
477                                 var action = this._actionSequence[this._indexOfNextAction];
478                                 this._indexOfNextAction++;
479                                 return action.invoke();
480                         }
481                 }
482         },
483         
484         addAll: function() {
485                 this._actionSequence = [];
486                 for(var i=0; i<arguments.length; i++) {
487                         if(typeof arguments[i] != 'object' && arguments[i].invoke === undefined) {
488                                 throw new Error("cannot add a method action that does not have an invoke() method");
489                         }
490                         this._actionSequence.push(arguments[i]);
491                 }
492         }
497  * Mock
498  */
499 function Mock(mockedType) {
500         if(mockedType === undefined || mockedType.prototype === undefined) {
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)'");
502         }
503         this._mockedType = mockedType.prototype;
504         this._expectedCallCount;
505         this._isRecordingExpectations = false;
506         this._expectedInvocations = [];
508         // setup proxy
509         var IntermediateClass = new Function();
510         IntermediateClass.prototype = mockedType.prototype;
511         var ChildClass = new Function();
512         ChildClass.prototype = new IntermediateClass();
513         this._proxy = new ChildClass();
514         this._proxy.mock = this;
515         
516         for(property in mockedType.prototype) {
517                 if(this._isPublicMethod(mockedType.prototype, property)) {
518                         var publicMethodName = property;
519                         this._proxy[publicMethodName] = this._createMockedMethod(publicMethodName);
520                         this[publicMethodName] = this._createExpectationRecordingMethod(publicMethodName);
521                 }
522         }
525 Mock.prototype = {
526         
527         proxy: function() {
528                 return this._proxy;
529         },
530         
531         expects: function(expectedCallCount) {
532                 this._expectedCallCount = expectedCallCount;
533                 this._isRecordingExpectations = true;
534                 this._isRecordingStubs = false;
535                 return this;
536         },
537         
538         stubs: function() {
539                 this._isRecordingExpectations = false;
540                 this._isRecordingStubs = true;
541                 return this;
542         },
543         
544         verify: function() {
545                 for(var i=0; i<this._expectedInvocations.length; i++) {
546                         var expectedInvocation = this._expectedInvocations[i];
547                         try {
548                                 expectedInvocation.verify();
549                         } catch(e) {
550                                 var failMsg = e.message+this._describeMockSetup();
551                                 throw new Mock4JSException(failMsg);
552                         }
553                 }
554         },
555         
556         _isPublicMethod: function(mockedType, property) {
557                 try {
558                         var isMethod = typeof(mockedType[property]) == 'function';
559                         var isPublic = property.charAt(0) != "_"; 
560                         return isMethod && isPublic;
561                 } catch(e) {
562                         return false;
563                 }
564         },
566         _createExpectationRecordingMethod: function(methodName) {
567                 return function() {
568                         // ensure all arguments are instances of ArgumentMatcher
569                         var expectedArgs = [];
570                         for(var i=0; i<arguments.length; i++) {
571                                 if(arguments[i] !== null && arguments[i] !== undefined && arguments[i].argumentMatches) {
572                                         expectedArgs[i] = arguments[i];
573                                 } else {
574                                         expectedArgs[i] = new MatchExactly(arguments[i]);
575                                 }
576                         }
577                         
578                         // create stub or expected invocation
579                         var expectedInvocation;
580                         if(this._isRecordingExpectations) {
581                                 expectedInvocation = new ExpectedInvocation(methodName, expectedArgs, this._expectedCallCount);
582                         } else {
583                                 expectedInvocation = new StubInvocation(methodName, expectedArgs, new StubActionSequence());
584                         }
585                         
586                         this._expectedInvocations.push(expectedInvocation);
587                         
588                         this._isRecordingExpectations = false;
589                         this._isRecordingStubs = false;
590                         return expectedInvocation;
591                 }
592         },
593         
594         _createMockedMethod: function(methodName) {
595                 return function() {
596                         // go through expectation list backwards to ensure later expectations override earlier ones
597                         for(var i=this.mock._expectedInvocations.length-1; i>=0; i--) {
598                                 var expectedInvocation = this.mock._expectedInvocations[i];
599                                 if(expectedInvocation.matches(methodName, arguments)) {
600                                         try {
601                                                 return expectedInvocation.invoked();
602                                         } catch(e) {
603                                                 if(e instanceof Mock4JSException) {
604                                                         throw new Mock4JSException(e.message+this.mock._describeMockSetup());
605                                                 } else {
606                                                         // the user setup the mock to throw a specific error, so don't modify the message
607                                                         throw e;
608                                                 }
609                                         }
610                                 }
611                         }
612                         var failMsg = "unexpected invocation: "+methodName+"("+Mock4JSUtil.join(arguments)+")"+this.mock._describeMockSetup();
613                         throw new Mock4JSException(failMsg);
614                 };
615         },
616         
617         _describeMockSetup: function() {
618                 var msg = "\nAllowed:";
619                 for(var i=0; i<this._expectedInvocations.length; i++) {
620                         var expectedInvocation = this._expectedInvocations[i];
621                         msg += "\n" + expectedInvocation.describe();
622                 }
623                 return msg;
624         }