Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
seleniumhq
GitHub Repository: seleniumhq/selenium
Path: blob/trunk/third_party/closure/goog/testing/mock.js
4506 views
1
/**
2
* @license
3
* Copyright The Closure Library Authors.
4
* SPDX-License-Identifier: Apache-2.0
5
*/
6
7
/**
8
* @fileoverview This file defines base classes used for creating mocks in
9
* JavaScript. The API was inspired by EasyMock.
10
*
11
* The basic API is:
12
* <ul>
13
* <li>Create an object to be mocked
14
* <li>Create a mock object, passing in the above object to the constructor
15
* <li>Set expectations by calling methods on the mock object
16
* <li>Call $replay() on the mock object
17
* <li>Pass the mock to code that will make real calls on it
18
* <li>Call $verify() to make sure that expectations were met
19
* </ul>
20
*
21
* For examples, please see the unit tests for LooseMock and StrictMock.
22
*
23
* Still TODO
24
* implement better (and pluggable) argument matching
25
* Have the exceptions for LooseMock show the number of expected/actual calls
26
* loose and strict mocks share a lot of code - move it to the base class
27
*/
28
29
goog.setTestOnly('goog.testing.Mock');
30
goog.provide('goog.testing.Mock');
31
goog.provide('goog.testing.MockExpectation');
32
33
goog.require('goog.Promise');
34
goog.require('goog.asserts');
35
goog.require('goog.object');
36
goog.require('goog.promise.Resolver');
37
goog.require('goog.testing.JsUnitException');
38
goog.require('goog.testing.MockInterface');
39
goog.require('goog.testing.mockmatchers');
40
41
42
43
/**
44
* This is a class that represents an expectation.
45
* @param {string} name The name of the method for this expectation.
46
* @constructor
47
* @final
48
*/
49
goog.testing.MockExpectation = function(name) {
50
'use strict';
51
/**
52
* The name of the method that is expected to be called.
53
* @type {string}
54
*/
55
this.name = name;
56
57
/**
58
* An array of error messages for expectations not met.
59
* @type {Array<string>}
60
*/
61
this.errorMessages = [];
62
};
63
64
65
/**
66
* The minimum number of times this method should be called.
67
* @type {number}
68
*/
69
goog.testing.MockExpectation.prototype.minCalls = 1;
70
71
72
/**
73
* The maximum number of times this method should be called.
74
* @type {number}
75
*/
76
goog.testing.MockExpectation.prototype.maxCalls = 1;
77
78
79
/**
80
* The value that this method should return.
81
* @type {*}
82
*/
83
goog.testing.MockExpectation.prototype.returnValue;
84
85
86
/**
87
* The value that will be thrown when the method is called
88
* @type {*}
89
*/
90
goog.testing.MockExpectation.prototype.exceptionToThrow;
91
92
93
/**
94
* The arguments that are expected to be passed to this function
95
* @type {Array<*>}
96
*/
97
goog.testing.MockExpectation.prototype.argumentList;
98
99
100
/**
101
* The number of times this method is called by real code.
102
* @type {number}
103
*/
104
goog.testing.MockExpectation.prototype.actualCalls = 0;
105
106
107
/**
108
* The number of times this method is called during the verification phase.
109
* @type {number}
110
*/
111
goog.testing.MockExpectation.prototype.verificationCalls = 0;
112
113
114
/**
115
* The function which will be executed when this method is called.
116
* Method arguments will be passed to this function, and return value
117
* of this function will be returned by the method.
118
* @type {Function}
119
*/
120
goog.testing.MockExpectation.prototype.toDo;
121
122
123
/**
124
* Allow expectation failures to include messages.
125
* @param {string} message The failure message.
126
*/
127
goog.testing.MockExpectation.prototype.addErrorMessage = function(message) {
128
'use strict';
129
this.errorMessages.push(message);
130
};
131
132
133
/**
134
* Get the error messages seen so far.
135
* @return {string} Error messages separated by \n.
136
*/
137
goog.testing.MockExpectation.prototype.getErrorMessage = function() {
138
'use strict';
139
return this.errorMessages.join('\n');
140
};
141
142
143
/**
144
* Get how many error messages have been seen so far.
145
* @return {number} Count of error messages.
146
*/
147
goog.testing.MockExpectation.prototype.getErrorMessageCount = function() {
148
'use strict';
149
return this.errorMessages.length;
150
};
151
152
153
154
/**
155
* The base class for a mock object.
156
* @param {Object|Function} objectToMock The object that should be mocked, or
157
* the constructor of an object to mock.
158
* @param {boolean=} opt_mockStaticMethods An optional argument denoting that
159
* a mock should be constructed from the static functions of a class.
160
* @param {boolean=} opt_createProxy An optional argument denoting that
161
* a proxy for the target mock should be created.
162
* @constructor
163
* @implements {goog.testing.MockInterface}
164
*/
165
goog.testing.Mock = function(
166
objectToMock, opt_mockStaticMethods, opt_createProxy) {
167
'use strict';
168
if (!goog.isObject(objectToMock) && typeof objectToMock !== 'function') {
169
throw new Error('objectToMock must be an object or constructor.');
170
}
171
if (opt_createProxy && !opt_mockStaticMethods &&
172
typeof objectToMock === 'function') {
173
/**
174
* @constructor
175
* @final
176
*/
177
var tempCtor = function() {};
178
goog.inherits(tempCtor, objectToMock);
179
this.$proxy = new tempCtor();
180
} else if (
181
opt_createProxy && opt_mockStaticMethods &&
182
typeof objectToMock === 'function') {
183
throw new Error('Cannot create a proxy when opt_mockStaticMethods is true');
184
} else if (opt_createProxy && typeof objectToMock !== 'function') {
185
throw new Error('Must have a constructor to create a proxy');
186
}
187
188
if (typeof objectToMock === 'function' && !opt_mockStaticMethods) {
189
this.$initializeFunctions_(objectToMock.prototype);
190
} else {
191
this.$initializeFunctions_(objectToMock);
192
}
193
this.$argumentListVerifiers_ = {};
194
195
/** @protected {?goog.promise.Resolver<undefined>} */
196
this.waitingForExpectations = null;
197
};
198
199
200
/**
201
* Option that may be passed when constructing function, method, and
202
* constructor mocks. Indicates that the expected calls should be accepted in
203
* any order.
204
* @const
205
* @type {number}
206
*/
207
goog.testing.Mock.LOOSE = 1;
208
209
210
/**
211
* Option that may be passed when constructing function, method, and
212
* constructor mocks. Indicates that the expected calls should be accepted in
213
* the recorded order only.
214
* @const
215
* @type {number}
216
*/
217
goog.testing.Mock.STRICT = 0;
218
219
220
/**
221
* Asserts that a mock object is in record mode. This avoids type system errors
222
* from mock expectations.
223
*
224
* Usage:
225
*
226
* ```
227
* const record = goog.require('goog.testing.Mock.record');
228
*
229
* record(mockObject).someMethod(ignoreArgument).$returns(42);
230
* record(mockFunction)(ignoreArgument).$returns(42);
231
* ```
232
*
233
* @param {?} obj A mock in record mode.
234
* @return {?} The same object.
235
*/
236
goog.testing.Mock.record = function(obj) {
237
'use strict';
238
// If the user passes a method of a mock object, grab the object.
239
const mockObj = obj.$$mockObj ? obj.$$mockObj : obj;
240
goog.asserts.assert(
241
mockObj.$recording_ !== undefined,
242
'%s is not a mock. Did you pass a real object to record()?', obj);
243
goog.asserts.assert(
244
mockObj.$recording_,
245
'Your mock is in replay mode. You can only call record(mock) before mock.$replay()');
246
return obj;
247
};
248
249
250
/**
251
* This array contains the name of the functions that are part of the base
252
* Object prototype.
253
* Basically a copy of goog.object.PROTOTYPE_FIELDS_.
254
* @const
255
* @type {!Array<string>}
256
* @private
257
*/
258
goog.testing.Mock.OBJECT_PROTOTYPE_FIELDS_ = [
259
'constructor', 'hasOwnProperty', 'isPrototypeOf', 'propertyIsEnumerable',
260
'toLocaleString', 'toString', 'valueOf'
261
];
262
263
264
/**
265
* This array contains the name of the functions that are part of the base
266
* Function prototype. The restricted field 'caller' and 'arguments' are
267
* excluded.
268
* @const
269
* @type {!Array<string>}
270
* @private
271
*/
272
goog.testing.Mock.FUNCTION_PROTOTYPE_FIELDS_ = ['apply', 'bind', 'call'];
273
274
275
/**
276
* A proxy for the mock. This can be used for dependency injection in lieu of
277
* the mock if the test requires a strict instanceof check.
278
* @type {?Object}
279
*/
280
goog.testing.Mock.prototype.$proxy = null;
281
282
283
/**
284
* Map of argument name to optional argument list verifier function.
285
* @type {Object}
286
*/
287
goog.testing.Mock.prototype.$argumentListVerifiers_;
288
289
290
/**
291
* Whether or not we are in recording mode.
292
* @type {boolean}
293
* @private
294
*/
295
goog.testing.Mock.prototype.$recording_ = true;
296
297
298
/**
299
* The expectation currently being created. All methods that modify the
300
* current expectation return the Mock object for easy chaining, so this is
301
* where we keep track of the expectation that's currently being modified.
302
* @type {goog.testing.MockExpectation}
303
* @protected
304
*/
305
goog.testing.Mock.prototype.$pendingExpectation;
306
307
308
/**
309
* First exception thrown by this mock; used in $verify.
310
* @type {?Object}
311
* @private
312
*/
313
goog.testing.Mock.prototype.$threwException_ = null;
314
315
316
/**
317
* Initializes the functions on the mock object.
318
* @param {Object} objectToMock The object being mocked.
319
* @private
320
*/
321
goog.testing.Mock.prototype.$initializeFunctions_ = function(objectToMock) {
322
'use strict';
323
// Gets the object properties.
324
var enumerableProperties = goog.object.getAllPropertyNames(
325
objectToMock, false /* opt_includeObjectPrototype */,
326
false /* opt_includeFunctionPrototype */);
327
328
if (typeof objectToMock === 'function') {
329
for (var i = 0; i < goog.testing.Mock.FUNCTION_PROTOTYPE_FIELDS_.length;
330
i++) {
331
var prop = goog.testing.Mock.FUNCTION_PROTOTYPE_FIELDS_[i];
332
// Look at b/6758711 if you're considering adding ALL properties to ALL
333
// mocks.
334
if (objectToMock[prop] !== Function.prototype[prop]) {
335
enumerableProperties.push(prop);
336
}
337
}
338
}
339
340
// The non enumerable properties are added if they override the ones in the
341
// Object prototype. This is due to the fact that IE8 does not enumerate any
342
// of the prototype Object functions even when overridden and mocking these is
343
// sometimes needed.
344
for (var i = 0; i < goog.testing.Mock.OBJECT_PROTOTYPE_FIELDS_.length; i++) {
345
var prop = goog.testing.Mock.OBJECT_PROTOTYPE_FIELDS_[i];
346
// Look at b/6758711 if you're considering adding ALL properties to ALL
347
// mocks.
348
if (objectToMock[prop] !== Object.prototype[prop]) {
349
enumerableProperties.push(prop);
350
}
351
}
352
353
// Adds the properties to the mock.
354
for (var i = 0; i < enumerableProperties.length; i++) {
355
var prop = enumerableProperties[i];
356
if (typeof objectToMock[prop] == 'function') {
357
this[prop] = goog.bind(this.$mockMethod, this, prop);
358
this[prop].$$mockObj = this; // Save a reference for record().
359
if (this.$proxy) {
360
this.$proxy[prop] = goog.bind(this.$mockMethod, this, prop);
361
}
362
}
363
}
364
};
365
366
367
/**
368
* Registers a verifier function to use when verifying method argument lists.
369
* @param {string} methodName The name of the method for which the verifierFn
370
* should be used.
371
* @param {Function} fn Argument list verifier function. Should take 2 argument
372
* arrays as arguments, and return true if they are considered equivalent.
373
* @return {!goog.testing.Mock} This mock object.
374
*/
375
goog.testing.Mock.prototype.$registerArgumentListVerifier = function(
376
methodName, fn) {
377
'use strict';
378
this.$argumentListVerifiers_[methodName] = fn;
379
return this;
380
};
381
382
383
/**
384
* The function that replaces all methods on the mock object.
385
* @param {string} name The name of the method being mocked.
386
* @return {*} In record mode, returns the mock object. In replay mode, returns
387
* whatever the creator of the mock set as the return value.
388
*/
389
goog.testing.Mock.prototype.$mockMethod = function(name) {
390
'use strict';
391
try {
392
// Shift off the name argument so that args contains the arguments to
393
// the mocked method.
394
var args = Array.prototype.slice.call(arguments, 1);
395
if (this.$recording_) {
396
this.$pendingExpectation = new goog.testing.MockExpectation(name);
397
this.$pendingExpectation.argumentList = args;
398
this.$recordExpectation();
399
return this;
400
} else {
401
return this.$recordCall(name, args);
402
}
403
} catch (ex) {
404
this.$recordAndThrow(ex, true /* rethrow */);
405
}
406
};
407
408
409
/**
410
* Records the currently pending expectation, intended to be overridden by a
411
* subclass.
412
* @protected
413
*/
414
goog.testing.Mock.prototype.$recordExpectation = function() {};
415
416
417
/**
418
* Records an actual method call, intended to be overridden by a
419
* subclass. The subclass must find the pending expectation and return the
420
* correct value.
421
* @param {string} name The name of the method being called.
422
* @param {Array<?>} args The arguments to the method.
423
* @return {*} The return expected by the mock.
424
* @protected
425
*/
426
goog.testing.Mock.prototype.$recordCall = function(name, args) {
427
'use strict';
428
return undefined;
429
};
430
431
432
/**
433
* If the expectation expects to throw, this method will throw.
434
* @param {goog.testing.MockExpectation} expectation The expectation.
435
*/
436
goog.testing.Mock.prototype.$maybeThrow = function(expectation) {
437
'use strict';
438
if (typeof expectation.exceptionToThrow != 'undefined') {
439
throw expectation.exceptionToThrow;
440
}
441
};
442
443
444
/**
445
* If this expectation defines a function to be called,
446
* it will be called and its result will be returned.
447
* Otherwise, if the expectation expects to throw, it will throw.
448
* Otherwise, this method will return defined value.
449
* @param {goog.testing.MockExpectation} expectation The expectation.
450
* @param {Array<?>} args The arguments to the method.
451
* @return {*} The return value expected by the mock.
452
*/
453
goog.testing.Mock.prototype.$do = function(expectation, args) {
454
'use strict';
455
if (typeof expectation.toDo == 'undefined') {
456
this.$maybeThrow(expectation);
457
return expectation.returnValue;
458
} else {
459
return expectation.toDo.apply(this, args);
460
}
461
};
462
463
464
/**
465
* Specifies a return value for the currently pending expectation.
466
* @param {*} val The return value.
467
* @return {!goog.testing.Mock} This mock object.
468
*/
469
goog.testing.Mock.prototype.$returns = function(val) {
470
'use strict';
471
this.$pendingExpectation.returnValue = val;
472
return this;
473
};
474
475
476
/**
477
* Specifies a value for the currently pending expectation to throw.
478
* @param {*} val The value to throw.
479
* @return {!goog.testing.Mock} This mock object.
480
*/
481
goog.testing.Mock.prototype.$throws = function(val) {
482
'use strict';
483
this.$pendingExpectation.exceptionToThrow = val;
484
return this;
485
};
486
487
488
/**
489
* Specifies a function to call for currently pending expectation.
490
* Note, that using this method overrides declarations made
491
* using $returns() and $throws() methods.
492
* @param {Function} func The function to call.
493
* @return {!goog.testing.Mock} This mock object.
494
*/
495
goog.testing.Mock.prototype.$does = function(func) {
496
'use strict';
497
this.$pendingExpectation.toDo = func;
498
return this;
499
};
500
501
502
/**
503
* Allows the expectation to be called 0 or 1 times.
504
* @return {!goog.testing.Mock} This mock object.
505
*/
506
goog.testing.Mock.prototype.$atMostOnce = function() {
507
'use strict';
508
this.$pendingExpectation.minCalls = 0;
509
this.$pendingExpectation.maxCalls = 1;
510
return this;
511
};
512
513
514
/**
515
* Allows the expectation to be called any number of times, as long as it's
516
* called once.
517
* @return {!goog.testing.Mock} This mock object.
518
*/
519
goog.testing.Mock.prototype.$atLeastOnce = function() {
520
'use strict';
521
this.$pendingExpectation.maxCalls = Infinity;
522
return this;
523
};
524
525
526
/**
527
* Allows the expectation to be called exactly once.
528
* @return {!goog.testing.Mock} This mock object.
529
*/
530
goog.testing.Mock.prototype.$once = function() {
531
'use strict';
532
this.$pendingExpectation.minCalls = 1;
533
this.$pendingExpectation.maxCalls = 1;
534
return this;
535
};
536
537
538
/**
539
* Disallows the expectation from being called.
540
* @return {!goog.testing.Mock} This mock object.
541
*/
542
goog.testing.Mock.prototype.$never = function() {
543
'use strict';
544
this.$pendingExpectation.minCalls = 0;
545
this.$pendingExpectation.maxCalls = 0;
546
return this;
547
};
548
549
550
/**
551
* Allows the expectation to be called any number of times.
552
* @return {!goog.testing.Mock} This mock object.
553
*/
554
goog.testing.Mock.prototype.$anyTimes = function() {
555
'use strict';
556
this.$pendingExpectation.minCalls = 0;
557
this.$pendingExpectation.maxCalls = Infinity;
558
return this;
559
};
560
561
562
/**
563
* Specifies the number of times the expectation should be called.
564
* @param {number} times The number of times this method will be called.
565
* @return {!goog.testing.Mock} This mock object.
566
*/
567
goog.testing.Mock.prototype.$times = function(times) {
568
'use strict';
569
this.$pendingExpectation.minCalls = times;
570
this.$pendingExpectation.maxCalls = times;
571
return this;
572
};
573
574
575
/**
576
* Switches from recording to replay mode.
577
* @override
578
*/
579
goog.testing.Mock.prototype.$replay = function() {
580
'use strict';
581
this.$recording_ = false;
582
};
583
584
585
/**
586
* Resets the state of this mock object. This clears all pending expectations
587
* without verifying, and puts the mock in recording mode.
588
* @override
589
*/
590
goog.testing.Mock.prototype.$reset = function() {
591
'use strict';
592
this.$recording_ = true;
593
this.$threwException_ = null;
594
delete this.$pendingExpectation;
595
if (this.waitingForExpectations) {
596
this.waitingForExpectations = null;
597
}
598
};
599
600
601
/**
602
* Throws an exception and records that an exception was thrown.
603
* @param {string} comment A short comment about the exception.
604
* @param {?string=} opt_message A longer message about the exception.
605
* @throws {Object} JsUnitException object.
606
* @protected
607
*/
608
goog.testing.Mock.prototype.$throwException = function(comment, opt_message) {
609
'use strict';
610
this.$recordAndThrow(new goog.testing.JsUnitException(comment, opt_message));
611
};
612
613
614
/**
615
* Throws an exception and records that an exception was thrown.
616
* @param {Object} ex Exception.
617
* @param {boolean=} rethrow True if this exception has already been thrown. If
618
* so, we should not report it to TestCase (since it was already reported at
619
* the original throw). This is necessary to avoid logging it twice, because
620
* assertThrowsJsUnitException only removes one record.
621
* @throws {Object} #ex.
622
* @protected
623
*/
624
goog.testing.Mock.prototype.$recordAndThrow = function(ex, rethrow) {
625
'use strict';
626
if (this.waitingForExpectations) {
627
this.waitingForExpectations.resolve();
628
}
629
if (this.$recording_) {
630
ex = new goog.testing.JsUnitException(
631
'Threw an exception while in record mode, did you $replay?',
632
ex.toString());
633
}
634
// If it's an assert exception, record it.
635
if (ex['isJsUnitException']) {
636
if (!this.$threwException_) {
637
// Only remember first exception thrown.
638
this.$threwException_ = ex;
639
}
640
641
// Don't fail if JSUnit isn't loaded. Instead, the test can catch the error
642
// normally. Other test frameworks won't get automatic failures if assertion
643
// errors are swallowed.
644
var getTestCase =
645
goog.getObjectByName('goog.testing.TestCase.getActiveTestCase');
646
var testCase = getTestCase && getTestCase();
647
if (testCase && !rethrow) {
648
testCase.raiseAssertionException(ex);
649
}
650
}
651
throw ex;
652
};
653
654
655
/** @override */
656
goog.testing.Mock.prototype.$waitAndVerify = function() {
657
'use strict';
658
goog.asserts.assert(
659
!this.$recording_,
660
'$waitAndVerify should be called after recording calls.');
661
this.waitingForExpectations = goog.Promise.withResolver();
662
var verify = goog.bind(this.$verify, this);
663
return this.waitingForExpectations.promise.then(function() {
664
'use strict';
665
return new goog.Promise(function(resolve, reject) {
666
'use strict';
667
setTimeout(function() {
668
'use strict';
669
try {
670
verify();
671
} catch (e) {
672
reject(e);
673
}
674
resolve();
675
}, 0);
676
});
677
});
678
};
679
680
681
/**
682
* Verify that all of the expectations were met. Should be overridden by
683
* subclasses.
684
* @override
685
*/
686
goog.testing.Mock.prototype.$verify = function() {
687
'use strict';
688
if (this.$threwException_) {
689
throw this.$threwException_;
690
}
691
};
692
693
694
/**
695
* Verifies that a method call matches an expectation.
696
* @param {goog.testing.MockExpectation} expectation The expectation to check.
697
* @param {string} name The name of the called method.
698
* @param {Array<*>?} args The arguments passed to the mock.
699
* @return {boolean} Whether the call matches the expectation.
700
*/
701
goog.testing.Mock.prototype.$verifyCall = function(expectation, name, args) {
702
'use strict';
703
if (expectation.name != name) {
704
return false;
705
}
706
var verifierFn =
707
this.$argumentListVerifiers_.hasOwnProperty(expectation.name) ?
708
this.$argumentListVerifiers_[expectation.name] :
709
goog.testing.mockmatchers.flexibleArrayMatcher;
710
711
return verifierFn(expectation.argumentList, args, expectation);
712
};
713
714
715
/**
716
* Render the provided argument array to a string to help
717
* clients with debugging tests.
718
* @param {Array<*>?} args The arguments passed to the mock.
719
* @return {string} Human-readable string.
720
*/
721
goog.testing.Mock.prototype.$argumentsAsString = function(args) {
722
'use strict';
723
var retVal = [];
724
for (var i = 0; i < args.length; i++) {
725
try {
726
retVal.push(goog.typeOf(args[i]));
727
} catch (e) {
728
retVal.push('[unknown]');
729
}
730
}
731
return '(' + retVal.join(', ') + ')';
732
};
733
734
735
/**
736
* Throw an exception based on an incorrect method call.
737
* @param {string} name Name of method called.
738
* @param {Array<*>?} args Arguments passed to the mock.
739
* @param {goog.testing.MockExpectation=} opt_expectation Expected next call,
740
* if any.
741
*/
742
goog.testing.Mock.prototype.$throwCallException = function(
743
name, args, opt_expectation) {
744
'use strict';
745
var errorStringBuffer = [];
746
var actualArgsString = this.$argumentsAsString(args);
747
var expectedArgsString = opt_expectation ?
748
this.$argumentsAsString(opt_expectation.argumentList) :
749
'';
750
751
if (opt_expectation && opt_expectation.name == name) {
752
errorStringBuffer.push(
753
'Bad arguments to ', name, '().\n', 'Actual: ', actualArgsString, '\n',
754
'Expected: ', expectedArgsString, '\n',
755
opt_expectation.getErrorMessage());
756
} else {
757
errorStringBuffer.push(
758
'Unexpected call to ', name, actualArgsString, '.',
759
'\nDid you forget to $replay?');
760
if (opt_expectation) {
761
errorStringBuffer.push(
762
'\nNext expected call was to ', opt_expectation.name,
763
expectedArgsString);
764
}
765
}
766
this.$throwException(errorStringBuffer.join(''));
767
};
768
769