mock.js 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667
  1. // Copyright 2008 The Closure Library Authors. All Rights Reserved.
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS-IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. /**
  15. * @fileoverview This file defines base classes used for creating mocks in
  16. * JavaScript. The API was inspired by EasyMock.
  17. *
  18. * The basic API is:
  19. * <ul>
  20. * <li>Create an object to be mocked
  21. * <li>Create a mock object, passing in the above object to the constructor
  22. * <li>Set expectations by calling methods on the mock object
  23. * <li>Call $replay() on the mock object
  24. * <li>Pass the mock to code that will make real calls on it
  25. * <li>Call $verify() to make sure that expectations were met
  26. * </ul>
  27. *
  28. * For examples, please see the unit tests for LooseMock and StrictMock.
  29. *
  30. * Still TODO
  31. * implement better (and pluggable) argument matching
  32. * Have the exceptions for LooseMock show the number of expected/actual calls
  33. * loose and strict mocks share a lot of code - move it to the base class
  34. *
  35. */
  36. goog.setTestOnly('goog.testing.Mock');
  37. goog.provide('goog.testing.Mock');
  38. goog.provide('goog.testing.MockExpectation');
  39. goog.require('goog.array');
  40. goog.require('goog.object');
  41. goog.require('goog.testing.JsUnitException');
  42. goog.require('goog.testing.MockInterface');
  43. goog.require('goog.testing.mockmatchers');
  44. /**
  45. * This is a class that represents an expectation.
  46. * @param {string} name The name of the method for this expectation.
  47. * @constructor
  48. * @final
  49. */
  50. goog.testing.MockExpectation = function(name) {
  51. /**
  52. * The name of the method that is expected to be called.
  53. * @type {string}
  54. */
  55. this.name = name;
  56. /**
  57. * An array of error messages for expectations not met.
  58. * @type {Array<string>}
  59. */
  60. this.errorMessages = [];
  61. };
  62. /**
  63. * The minimum number of times this method should be called.
  64. * @type {number}
  65. */
  66. goog.testing.MockExpectation.prototype.minCalls = 1;
  67. /**
  68. * The maximum number of times this method should be called.
  69. * @type {number}
  70. */
  71. goog.testing.MockExpectation.prototype.maxCalls = 1;
  72. /**
  73. * The value that this method should return.
  74. * @type {*}
  75. */
  76. goog.testing.MockExpectation.prototype.returnValue;
  77. /**
  78. * The value that will be thrown when the method is called
  79. * @type {*}
  80. */
  81. goog.testing.MockExpectation.prototype.exceptionToThrow;
  82. /**
  83. * The arguments that are expected to be passed to this function
  84. * @type {Array<*>}
  85. */
  86. goog.testing.MockExpectation.prototype.argumentList;
  87. /**
  88. * The number of times this method is called by real code.
  89. * @type {number}
  90. */
  91. goog.testing.MockExpectation.prototype.actualCalls = 0;
  92. /**
  93. * The number of times this method is called during the verification phase.
  94. * @type {number}
  95. */
  96. goog.testing.MockExpectation.prototype.verificationCalls = 0;
  97. /**
  98. * The function which will be executed when this method is called.
  99. * Method arguments will be passed to this function, and return value
  100. * of this function will be returned by the method.
  101. * @type {Function}
  102. */
  103. goog.testing.MockExpectation.prototype.toDo;
  104. /**
  105. * Allow expectation failures to include messages.
  106. * @param {string} message The failure message.
  107. */
  108. goog.testing.MockExpectation.prototype.addErrorMessage = function(message) {
  109. this.errorMessages.push(message);
  110. };
  111. /**
  112. * Get the error messages seen so far.
  113. * @return {string} Error messages separated by \n.
  114. */
  115. goog.testing.MockExpectation.prototype.getErrorMessage = function() {
  116. return this.errorMessages.join('\n');
  117. };
  118. /**
  119. * Get how many error messages have been seen so far.
  120. * @return {number} Count of error messages.
  121. */
  122. goog.testing.MockExpectation.prototype.getErrorMessageCount = function() {
  123. return this.errorMessages.length;
  124. };
  125. /**
  126. * The base class for a mock object.
  127. * @param {Object|Function} objectToMock The object that should be mocked, or
  128. * the constructor of an object to mock.
  129. * @param {boolean=} opt_mockStaticMethods An optional argument denoting that
  130. * a mock should be constructed from the static functions of a class.
  131. * @param {boolean=} opt_createProxy An optional argument denoting that
  132. * a proxy for the target mock should be created.
  133. * @constructor
  134. * @implements {goog.testing.MockInterface}
  135. */
  136. goog.testing.Mock = function(
  137. objectToMock, opt_mockStaticMethods, opt_createProxy) {
  138. if (!goog.isObject(objectToMock) && !goog.isFunction(objectToMock)) {
  139. throw new Error('objectToMock must be an object or constructor.');
  140. }
  141. if (opt_createProxy && !opt_mockStaticMethods &&
  142. goog.isFunction(objectToMock)) {
  143. /**
  144. * @constructor
  145. * @final
  146. */
  147. var tempCtor = function() {};
  148. goog.inherits(tempCtor, objectToMock);
  149. this.$proxy = new tempCtor();
  150. } else if (
  151. opt_createProxy && opt_mockStaticMethods &&
  152. goog.isFunction(objectToMock)) {
  153. throw Error('Cannot create a proxy when opt_mockStaticMethods is true');
  154. } else if (opt_createProxy && !goog.isFunction(objectToMock)) {
  155. throw Error('Must have a constructor to create a proxy');
  156. }
  157. if (goog.isFunction(objectToMock) && !opt_mockStaticMethods) {
  158. this.$initializeFunctions_(objectToMock.prototype);
  159. } else {
  160. this.$initializeFunctions_(objectToMock);
  161. }
  162. this.$argumentListVerifiers_ = {};
  163. };
  164. /**
  165. * Option that may be passed when constructing function, method, and
  166. * constructor mocks. Indicates that the expected calls should be accepted in
  167. * any order.
  168. * @const
  169. * @type {number}
  170. */
  171. goog.testing.Mock.LOOSE = 1;
  172. /**
  173. * Option that may be passed when constructing function, method, and
  174. * constructor mocks. Indicates that the expected calls should be accepted in
  175. * the recorded order only.
  176. * @const
  177. * @type {number}
  178. */
  179. goog.testing.Mock.STRICT = 0;
  180. /**
  181. * This array contains the name of the functions that are part of the base
  182. * Object prototype.
  183. * Basically a copy of goog.object.PROTOTYPE_FIELDS_.
  184. * @const
  185. * @type {!Array<string>}
  186. * @private
  187. */
  188. goog.testing.Mock.OBJECT_PROTOTYPE_FIELDS_ = [
  189. 'constructor', 'hasOwnProperty', 'isPrototypeOf', 'propertyIsEnumerable',
  190. 'toLocaleString', 'toString', 'valueOf'
  191. ];
  192. /**
  193. * This array contains the name of the functions that are part of the base
  194. * Function prototype. The restricted field 'caller' and 'arguments' are
  195. * excluded.
  196. * @const
  197. * @type {!Array<string>}
  198. * @private
  199. */
  200. goog.testing.Mock.FUNCTION_PROTOTYPE_FIELDS_ = ['apply', 'bind', 'call'];
  201. /**
  202. * A proxy for the mock. This can be used for dependency injection in lieu of
  203. * the mock if the test requires a strict instanceof check.
  204. * @type {Object}
  205. */
  206. goog.testing.Mock.prototype.$proxy = null;
  207. /**
  208. * Map of argument name to optional argument list verifier function.
  209. * @type {Object}
  210. */
  211. goog.testing.Mock.prototype.$argumentListVerifiers_;
  212. /**
  213. * Whether or not we are in recording mode.
  214. * @type {boolean}
  215. * @private
  216. */
  217. goog.testing.Mock.prototype.$recording_ = true;
  218. /**
  219. * The expectation currently being created. All methods that modify the
  220. * current expectation return the Mock object for easy chaining, so this is
  221. * where we keep track of the expectation that's currently being modified.
  222. * @type {goog.testing.MockExpectation}
  223. * @protected
  224. */
  225. goog.testing.Mock.prototype.$pendingExpectation;
  226. /**
  227. * First exception thrown by this mock; used in $verify.
  228. * @type {Object}
  229. * @private
  230. */
  231. goog.testing.Mock.prototype.$threwException_ = null;
  232. /**
  233. * Initializes the functions on the mock object.
  234. * @param {Object} objectToMock The object being mocked.
  235. * @private
  236. */
  237. goog.testing.Mock.prototype.$initializeFunctions_ = function(objectToMock) {
  238. // Gets the object properties.
  239. var enumerableProperties = goog.object.getAllPropertyNames(
  240. objectToMock, false /* opt_includeObjectPrototype */,
  241. false /* opt_includeFunctionPrototype */);
  242. if (goog.isFunction(objectToMock)) {
  243. for (var i = 0; i < goog.testing.Mock.FUNCTION_PROTOTYPE_FIELDS_.length;
  244. i++) {
  245. var prop = goog.testing.Mock.FUNCTION_PROTOTYPE_FIELDS_[i];
  246. // Look at b/6758711 if you're considering adding ALL properties to ALL
  247. // mocks.
  248. if (objectToMock[prop] !== Function.prototype[prop]) {
  249. enumerableProperties.push(prop);
  250. }
  251. }
  252. }
  253. // The non enumerable properties are added if they override the ones in the
  254. // Object prototype. This is due to the fact that IE8 does not enumerate any
  255. // of the prototype Object functions even when overriden and mocking these is
  256. // sometimes needed.
  257. for (var i = 0; i < goog.testing.Mock.OBJECT_PROTOTYPE_FIELDS_.length; i++) {
  258. var prop = goog.testing.Mock.OBJECT_PROTOTYPE_FIELDS_[i];
  259. // Look at b/6758711 if you're considering adding ALL properties to ALL
  260. // mocks.
  261. if (objectToMock[prop] !== Object.prototype[prop]) {
  262. enumerableProperties.push(prop);
  263. }
  264. }
  265. // Adds the properties to the mock.
  266. for (var i = 0; i < enumerableProperties.length; i++) {
  267. var prop = enumerableProperties[i];
  268. if (typeof objectToMock[prop] == 'function') {
  269. this[prop] = goog.bind(this.$mockMethod, this, prop);
  270. if (this.$proxy) {
  271. this.$proxy[prop] = goog.bind(this.$mockMethod, this, prop);
  272. }
  273. }
  274. }
  275. };
  276. /**
  277. * Registers a verifier function to use when verifying method argument lists.
  278. * @param {string} methodName The name of the method for which the verifierFn
  279. * should be used.
  280. * @param {Function} fn Argument list verifier function. Should take 2 argument
  281. * arrays as arguments, and return true if they are considered equivalent.
  282. * @return {!goog.testing.Mock} This mock object.
  283. */
  284. goog.testing.Mock.prototype.$registerArgumentListVerifier = function(
  285. methodName, fn) {
  286. this.$argumentListVerifiers_[methodName] = fn;
  287. return this;
  288. };
  289. /**
  290. * The function that replaces all methods on the mock object.
  291. * @param {string} name The name of the method being mocked.
  292. * @return {*} In record mode, returns the mock object. In replay mode, returns
  293. * whatever the creator of the mock set as the return value.
  294. */
  295. goog.testing.Mock.prototype.$mockMethod = function(name) {
  296. try {
  297. // Shift off the name argument so that args contains the arguments to
  298. // the mocked method.
  299. var args = goog.array.slice(arguments, 1);
  300. if (this.$recording_) {
  301. this.$pendingExpectation = new goog.testing.MockExpectation(name);
  302. this.$pendingExpectation.argumentList = args;
  303. this.$recordExpectation();
  304. return this;
  305. } else {
  306. return this.$recordCall(name, args);
  307. }
  308. } catch (ex) {
  309. this.$recordAndThrow(ex);
  310. }
  311. };
  312. /**
  313. * Records the currently pending expectation, intended to be overridden by a
  314. * subclass.
  315. * @protected
  316. */
  317. goog.testing.Mock.prototype.$recordExpectation = function() {};
  318. /**
  319. * Records an actual method call, intended to be overridden by a
  320. * subclass. The subclass must find the pending expectation and return the
  321. * correct value.
  322. * @param {string} name The name of the method being called.
  323. * @param {Array<?>} args The arguments to the method.
  324. * @return {*} The return expected by the mock.
  325. * @protected
  326. */
  327. goog.testing.Mock.prototype.$recordCall = function(name, args) {
  328. return undefined;
  329. };
  330. /**
  331. * If the expectation expects to throw, this method will throw.
  332. * @param {goog.testing.MockExpectation} expectation The expectation.
  333. */
  334. goog.testing.Mock.prototype.$maybeThrow = function(expectation) {
  335. if (typeof expectation.exceptionToThrow != 'undefined') {
  336. throw expectation.exceptionToThrow;
  337. }
  338. };
  339. /**
  340. * If this expectation defines a function to be called,
  341. * it will be called and its result will be returned.
  342. * Otherwise, if the expectation expects to throw, it will throw.
  343. * Otherwise, this method will return defined value.
  344. * @param {goog.testing.MockExpectation} expectation The expectation.
  345. * @param {Array<?>} args The arguments to the method.
  346. * @return {*} The return value expected by the mock.
  347. */
  348. goog.testing.Mock.prototype.$do = function(expectation, args) {
  349. if (typeof expectation.toDo == 'undefined') {
  350. this.$maybeThrow(expectation);
  351. return expectation.returnValue;
  352. } else {
  353. return expectation.toDo.apply(this, args);
  354. }
  355. };
  356. /**
  357. * Specifies a return value for the currently pending expectation.
  358. * @param {*} val The return value.
  359. * @return {!goog.testing.Mock} This mock object.
  360. */
  361. goog.testing.Mock.prototype.$returns = function(val) {
  362. this.$pendingExpectation.returnValue = val;
  363. return this;
  364. };
  365. /**
  366. * Specifies a value for the currently pending expectation to throw.
  367. * @param {*} val The value to throw.
  368. * @return {!goog.testing.Mock} This mock object.
  369. */
  370. goog.testing.Mock.prototype.$throws = function(val) {
  371. this.$pendingExpectation.exceptionToThrow = val;
  372. return this;
  373. };
  374. /**
  375. * Specifies a function to call for currently pending expectation.
  376. * Note, that using this method overrides declarations made
  377. * using $returns() and $throws() methods.
  378. * @param {Function} func The function to call.
  379. * @return {!goog.testing.Mock} This mock object.
  380. */
  381. goog.testing.Mock.prototype.$does = function(func) {
  382. this.$pendingExpectation.toDo = func;
  383. return this;
  384. };
  385. /**
  386. * Allows the expectation to be called 0 or 1 times.
  387. * @return {!goog.testing.Mock} This mock object.
  388. */
  389. goog.testing.Mock.prototype.$atMostOnce = function() {
  390. this.$pendingExpectation.minCalls = 0;
  391. this.$pendingExpectation.maxCalls = 1;
  392. return this;
  393. };
  394. /**
  395. * Allows the expectation to be called any number of times, as long as it's
  396. * called once.
  397. * @return {!goog.testing.Mock} This mock object.
  398. */
  399. goog.testing.Mock.prototype.$atLeastOnce = function() {
  400. this.$pendingExpectation.maxCalls = Infinity;
  401. return this;
  402. };
  403. /**
  404. * Allows the expectation to be called exactly once.
  405. * @return {!goog.testing.Mock} This mock object.
  406. */
  407. goog.testing.Mock.prototype.$once = function() {
  408. this.$pendingExpectation.minCalls = 1;
  409. this.$pendingExpectation.maxCalls = 1;
  410. return this;
  411. };
  412. /**
  413. * Disallows the expectation from being called.
  414. * @return {!goog.testing.Mock} This mock object.
  415. */
  416. goog.testing.Mock.prototype.$never = function() {
  417. this.$pendingExpectation.minCalls = 0;
  418. this.$pendingExpectation.maxCalls = 0;
  419. return this;
  420. };
  421. /**
  422. * Allows the expectation to be called any number of times.
  423. * @return {!goog.testing.Mock} This mock object.
  424. */
  425. goog.testing.Mock.prototype.$anyTimes = function() {
  426. this.$pendingExpectation.minCalls = 0;
  427. this.$pendingExpectation.maxCalls = Infinity;
  428. return this;
  429. };
  430. /**
  431. * Specifies the number of times the expectation should be called.
  432. * @param {number} times The number of times this method will be called.
  433. * @return {!goog.testing.Mock} This mock object.
  434. */
  435. goog.testing.Mock.prototype.$times = function(times) {
  436. this.$pendingExpectation.minCalls = times;
  437. this.$pendingExpectation.maxCalls = times;
  438. return this;
  439. };
  440. /**
  441. * Switches from recording to replay mode.
  442. * @override
  443. */
  444. goog.testing.Mock.prototype.$replay = function() {
  445. this.$recording_ = false;
  446. };
  447. /**
  448. * Resets the state of this mock object. This clears all pending expectations
  449. * without verifying, and puts the mock in recording mode.
  450. * @override
  451. */
  452. goog.testing.Mock.prototype.$reset = function() {
  453. this.$recording_ = true;
  454. this.$threwException_ = null;
  455. delete this.$pendingExpectation;
  456. };
  457. /**
  458. * Throws an exception and records that an exception was thrown.
  459. * @param {string} comment A short comment about the exception.
  460. * @param {?string=} opt_message A longer message about the exception.
  461. * @throws {Object} JsUnitException object.
  462. * @protected
  463. */
  464. goog.testing.Mock.prototype.$throwException = function(comment, opt_message) {
  465. this.$recordAndThrow(new goog.testing.JsUnitException(comment, opt_message));
  466. };
  467. /**
  468. * Throws an exception and records that an exception was thrown.
  469. * @param {Object} ex Exception.
  470. * @throws {Object} #ex.
  471. * @protected
  472. */
  473. goog.testing.Mock.prototype.$recordAndThrow = function(ex) {
  474. // If it's an assert exception, record it.
  475. if (ex['isJsUnitException']) {
  476. var testRunner = goog.global['G_testRunner'];
  477. if (testRunner) {
  478. var logTestFailureFunction = testRunner['logTestFailure'];
  479. if (logTestFailureFunction) {
  480. logTestFailureFunction.call(testRunner, ex);
  481. }
  482. }
  483. if (!this.$threwException_) {
  484. // Only remember first exception thrown.
  485. this.$threwException_ = ex;
  486. }
  487. }
  488. throw ex;
  489. };
  490. /**
  491. * Verify that all of the expectations were met. Should be overridden by
  492. * subclasses.
  493. * @override
  494. */
  495. goog.testing.Mock.prototype.$verify = function() {
  496. if (this.$threwException_) {
  497. throw this.$threwException_;
  498. }
  499. };
  500. /**
  501. * Verifies that a method call matches an expectation.
  502. * @param {goog.testing.MockExpectation} expectation The expectation to check.
  503. * @param {string} name The name of the called method.
  504. * @param {Array<*>?} args The arguments passed to the mock.
  505. * @return {boolean} Whether the call matches the expectation.
  506. */
  507. goog.testing.Mock.prototype.$verifyCall = function(expectation, name, args) {
  508. if (expectation.name != name) {
  509. return false;
  510. }
  511. var verifierFn =
  512. this.$argumentListVerifiers_.hasOwnProperty(expectation.name) ?
  513. this.$argumentListVerifiers_[expectation.name] :
  514. goog.testing.mockmatchers.flexibleArrayMatcher;
  515. return verifierFn(expectation.argumentList, args, expectation);
  516. };
  517. /**
  518. * Render the provided argument array to a string to help
  519. * clients with debugging tests.
  520. * @param {Array<*>?} args The arguments passed to the mock.
  521. * @return {string} Human-readable string.
  522. */
  523. goog.testing.Mock.prototype.$argumentsAsString = function(args) {
  524. var retVal = [];
  525. for (var i = 0; i < args.length; i++) {
  526. try {
  527. retVal.push(goog.typeOf(args[i]));
  528. } catch (e) {
  529. retVal.push('[unknown]');
  530. }
  531. }
  532. return '(' + retVal.join(', ') + ')';
  533. };
  534. /**
  535. * Throw an exception based on an incorrect method call.
  536. * @param {string} name Name of method called.
  537. * @param {Array<*>?} args Arguments passed to the mock.
  538. * @param {goog.testing.MockExpectation=} opt_expectation Expected next call,
  539. * if any.
  540. */
  541. goog.testing.Mock.prototype.$throwCallException = function(
  542. name, args, opt_expectation) {
  543. var errorStringBuffer = [];
  544. var actualArgsString = this.$argumentsAsString(args);
  545. var expectedArgsString = opt_expectation ?
  546. this.$argumentsAsString(opt_expectation.argumentList) :
  547. '';
  548. if (opt_expectation && opt_expectation.name == name) {
  549. errorStringBuffer.push(
  550. 'Bad arguments to ', name, '().\n', 'Actual: ', actualArgsString, '\n',
  551. 'Expected: ', expectedArgsString, '\n',
  552. opt_expectation.getErrorMessage());
  553. } else {
  554. errorStringBuffer.push('Unexpected call to ', name, actualArgsString, '.');
  555. if (opt_expectation) {
  556. errorStringBuffer.push(
  557. '\nNext expected call was to ', opt_expectation.name,
  558. expectedArgsString);
  559. }
  560. }
  561. this.$throwException(errorStringBuffer.join(''));
  562. };