mock.js 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980
  1. // Copyright 2012 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 Provides a mocking framework in Closure to make unit tests easy
  16. * to write and understand. The methods provided here can be used to replace
  17. * implementations of existing objects with 'mock' objects to abstract out
  18. * external services and dependencies thereby isolating the code under test.
  19. * Apart from mocking, methods are also provided to just monitor calls to an
  20. * object (spying) and returning specific values for some or all the inputs to
  21. * methods (stubbing).
  22. *
  23. * Design doc : http://go/closuremock
  24. *
  25. */
  26. goog.provide('goog.labs.mock');
  27. goog.provide('goog.labs.mock.VerificationError');
  28. goog.require('goog.array');
  29. goog.require('goog.asserts');
  30. goog.require('goog.debug');
  31. goog.require('goog.debug.Error');
  32. goog.require('goog.functions');
  33. goog.require('goog.labs.mock.verification');
  34. goog.require('goog.labs.mock.verification.VerificationMode');
  35. goog.require('goog.object');
  36. goog.setTestOnly('goog.labs.mock');
  37. /**
  38. * Mocks a given object or class.
  39. *
  40. * @param {!Object} objectOrClass An instance or a constructor of a class to be
  41. * mocked.
  42. * @return {!Object} The mocked object.
  43. */
  44. goog.labs.mock.mock = function(objectOrClass) {
  45. // Go over properties of 'objectOrClass' and create a MockManager to
  46. // be used for stubbing out calls to methods.
  47. var mockObjectManager = new goog.labs.mock.MockObjectManager_(objectOrClass);
  48. var mockedObject = mockObjectManager.getMockedItem();
  49. goog.asserts.assertObject(mockedObject);
  50. return /** @type {!Object} */ (mockedObject);
  51. };
  52. /**
  53. * Mocks a given function.
  54. *
  55. * @param {!Function} func A function to be mocked.
  56. * @return {!Function} The mocked function.
  57. */
  58. goog.labs.mock.mockFunction = function(func) {
  59. var mockFuncManager = new goog.labs.mock.MockFunctionManager_(func);
  60. var mockedFunction = mockFuncManager.getMockedItem();
  61. goog.asserts.assertFunction(mockedFunction);
  62. return /** @type {!Function} */ (mockedFunction);
  63. };
  64. /**
  65. * Mocks a given constructor.
  66. *
  67. * @param {!Function} ctor A constructor function to be mocked.
  68. * @return {!Function} The mocked constructor.
  69. */
  70. goog.labs.mock.mockConstructor = function(ctor) {
  71. var mockCtor = goog.labs.mock.mockFunction(ctor);
  72. // Copy class members from the real constructor to the mock. Do not copy
  73. // the closure superClass_ property (see goog.inherits), the built-in
  74. // prototype property, or properties added to Function.prototype
  75. for (var property in ctor) {
  76. if (property != 'superClass_' && property != 'prototype' &&
  77. ctor.hasOwnProperty(property)) {
  78. mockCtor[property] = ctor[property];
  79. }
  80. }
  81. return mockCtor;
  82. };
  83. /**
  84. * Spies on a given object.
  85. *
  86. * @param {!Object} obj The object to be spied on.
  87. * @return {!Object} The spy object.
  88. */
  89. goog.labs.mock.spy = function(obj) {
  90. // Go over properties of 'obj' and create a MockSpyManager_ to
  91. // be used for spying on calls to methods.
  92. var mockSpyManager = new goog.labs.mock.MockSpyManager_(obj);
  93. var spyObject = mockSpyManager.getMockedItem();
  94. goog.asserts.assert(spyObject);
  95. return spyObject;
  96. };
  97. /**
  98. * Returns an object that can be used to verify calls to specific methods of a
  99. * given mock.
  100. *
  101. * @param {!Object} obj The mocked object.
  102. * @param {!goog.labs.mock.verification.VerificationMode=} opt_verificationMode The mode
  103. * under which to verify invocations.
  104. * @return {!Object} The verifier.
  105. */
  106. goog.labs.mock.verify = function(obj, opt_verificationMode) {
  107. var mode = opt_verificationMode || goog.labs.mock.verification.atLeast(1);
  108. obj.$verificationModeSetter(mode);
  109. return obj.$callVerifier;
  110. };
  111. /**
  112. * Returns a name to identify a function. Named functions return their names,
  113. * unnamed functions return a string of the form '#anonymous{ID}' where ID is
  114. * a unique identifier for each anonymous function.
  115. * @private
  116. * @param {!Function} func The function.
  117. * @return {string} The function name.
  118. */
  119. goog.labs.mock.getFunctionName_ = function(func) {
  120. var funcName = goog.debug.getFunctionName(func);
  121. if (funcName == '' || funcName == '[Anonymous]') {
  122. funcName = '#anonymous' + goog.labs.mock.getUid(func);
  123. }
  124. return funcName;
  125. };
  126. /**
  127. * Returns a nicely formatted, readble representation of a method call.
  128. * @private
  129. * @param {string} methodName The name of the method.
  130. * @param {Array<?>=} opt_args The method arguments.
  131. * @return {string} The string representation of the method call.
  132. */
  133. goog.labs.mock.formatMethodCall_ = function(methodName, opt_args) {
  134. opt_args = opt_args || [];
  135. opt_args = goog.array.map(opt_args, function(arg) {
  136. if (goog.isFunction(arg)) {
  137. var funcName = goog.labs.mock.getFunctionName_(arg);
  138. return '<function ' + funcName + '>';
  139. } else {
  140. var isObjectWithClass = goog.isObject(arg) && !goog.isFunction(arg) &&
  141. !goog.isArray(arg) && arg.constructor != Object;
  142. if (isObjectWithClass) {
  143. return arg.toString();
  144. }
  145. return goog.labs.mock.formatValue_(arg);
  146. }
  147. });
  148. return methodName + '(' + opt_args.join(', ') + ')';
  149. };
  150. /**
  151. * An array to store objects for unique id generation.
  152. * @private
  153. * @type {!Array<!Object>}
  154. */
  155. goog.labs.mock.uid_ = [];
  156. /**
  157. * A unique Id generator that does not modify the object.
  158. * @param {Object!} obj The object whose unique ID we want to generate.
  159. * @return {number} an unique id for the object.
  160. */
  161. goog.labs.mock.getUid = function(obj) {
  162. var index = goog.array.indexOf(goog.labs.mock.uid_, obj);
  163. if (index == -1) {
  164. index = goog.labs.mock.uid_.length;
  165. goog.labs.mock.uid_.push(obj);
  166. }
  167. return index;
  168. };
  169. /**
  170. * This is just another implementation of goog.debug.deepExpose with a more
  171. * compact format.
  172. * @private
  173. * @param {*} obj The object whose string representation will be returned.
  174. * @param {boolean=} opt_id Whether to include the id of objects or not.
  175. * Defaults to true.
  176. * @return {string} The string representation of the object.
  177. */
  178. goog.labs.mock.formatValue_ = function(obj, opt_id) {
  179. var id = goog.isDef(opt_id) ? opt_id : true;
  180. var previous = [];
  181. var output = [];
  182. var helper = function(obj) {
  183. var indentMultiline = function(output) {
  184. return output.replace(/\n/g, '\n');
  185. };
  186. try {
  187. if (!goog.isDef(obj)) {
  188. output.push('undefined');
  189. } else if (goog.isNull(obj)) {
  190. output.push('NULL');
  191. } else if (goog.isString(obj)) {
  192. output.push('"' + indentMultiline(obj) + '"');
  193. } else if (goog.isFunction(obj)) {
  194. var funcName = goog.labs.mock.getFunctionName_(obj);
  195. output.push('<function ' + funcName + '>');
  196. } else if (goog.isObject(obj)) {
  197. if (goog.array.contains(previous, obj)) {
  198. if (id) {
  199. output.push(
  200. '<recursive/dupe obj_' + goog.labs.mock.getUid(obj) + '>');
  201. } else {
  202. output.push('<recursive/dupe>');
  203. }
  204. } else {
  205. previous.push(obj);
  206. output.push('{');
  207. for (var x in obj) {
  208. output.push(' ');
  209. output.push(
  210. '"' + x + '"' +
  211. ':');
  212. helper(obj[x]);
  213. }
  214. if (id) {
  215. output.push(' _id:' + goog.labs.mock.getUid(obj));
  216. }
  217. output.push('}');
  218. }
  219. } else {
  220. output.push(obj);
  221. }
  222. } catch (e) {
  223. output.push('*** ' + e + ' ***');
  224. }
  225. };
  226. helper(obj);
  227. return output.join('')
  228. .replace(/"closure_uid_\d+"/g, '_id')
  229. .replace(/{ /g, '{');
  230. };
  231. /**
  232. * Error thrown when verification failed.
  233. *
  234. * @param {Array<!goog.labs.mock.MethodBinding_>} recordedCalls
  235. * The recorded calls that didn't match the expectation.
  236. * @param {string} methodName The expected method call.
  237. * @param {!goog.labs.mock.verification.VerificationMode} verificationMode The
  238. * expected verification mode which failed verification.
  239. * @param {!Array<?>} args The expected arguments.
  240. * @constructor
  241. * @extends {goog.debug.Error}
  242. * @final
  243. */
  244. goog.labs.mock.VerificationError = function(
  245. recordedCalls, methodName, verificationMode, args) {
  246. var msg = goog.labs.mock.VerificationError.getVerificationErrorMsg_(
  247. recordedCalls, methodName, verificationMode, args);
  248. goog.labs.mock.VerificationError.base(this, 'constructor', msg);
  249. };
  250. goog.inherits(goog.labs.mock.VerificationError, goog.debug.Error);
  251. /** @override */
  252. goog.labs.mock.VerificationError.prototype.name = 'VerificationError';
  253. /**
  254. * This array contains the name of the functions that are part of the base
  255. * Object prototype.
  256. * Basically a copy of goog.object.PROTOTYPE_FIELDS_.
  257. * @const
  258. * @type {!Array<string>}
  259. * @private
  260. */
  261. goog.labs.mock.PROTOTYPE_FIELDS_ = [
  262. 'constructor', 'hasOwnProperty', 'isPrototypeOf', 'propertyIsEnumerable',
  263. 'toLocaleString', 'toString', 'valueOf'
  264. ];
  265. /**
  266. * Constructs a descriptive error message for an expected method call.
  267. * @private
  268. * @param {Array<!goog.labs.mock.MethodBinding_>} recordedCalls
  269. * The recorded calls that didn't match the expectation.
  270. * @param {string} methodName The expected method call.
  271. * @param {!goog.labs.mock.verification.VerificationMode} verificationMode The
  272. * expected verification mode that failed verification.
  273. * @param {!Array<?>} args The expected arguments.
  274. * @return {string} The error message.
  275. */
  276. goog.labs.mock.VerificationError.getVerificationErrorMsg_ = function(
  277. recordedCalls, methodName, verificationMode, args) {
  278. recordedCalls = goog.array.filter(recordedCalls, function(binding) {
  279. return binding.getMethodName() == methodName;
  280. });
  281. var expected = goog.labs.mock.formatMethodCall_(methodName, args);
  282. var msg =
  283. '\nExpected: ' + expected.toString() + ' ' + verificationMode.describe();
  284. msg += '\nRecorded: ';
  285. if (recordedCalls.length > 0) {
  286. msg += recordedCalls.join(',\n ');
  287. } else {
  288. msg += 'No recorded calls';
  289. }
  290. return msg;
  291. };
  292. /**
  293. * Base class that provides basic functionality for creating, adding and
  294. * finding bindings, offering an executor method that is called when a call to
  295. * the stub is made, an array to hold the bindings and the mocked item, among
  296. * other things.
  297. *
  298. * @constructor
  299. * @struct
  300. * @private
  301. */
  302. goog.labs.mock.MockManager_ = function() {
  303. /**
  304. * Proxies the methods for the mocked object or class to execute the stubs.
  305. * @type {!Object}
  306. * @protected
  307. */
  308. this.mockedItem = {};
  309. /**
  310. * A reference to the object or function being mocked.
  311. * @type {Object|Function}
  312. * @protected
  313. */
  314. this.mockee = null;
  315. /**
  316. * Holds the stub bindings established so far.
  317. * @protected
  318. */
  319. this.methodBindings = [];
  320. /**
  321. * Holds a reference to the binder used to define stubs.
  322. * @protected
  323. */
  324. this.$stubBinder = null;
  325. /**
  326. * Record method calls with no stub definitions.
  327. * @type {!Array<!goog.labs.mock.MethodBinding_>}
  328. * @private
  329. */
  330. this.callRecords_ = [];
  331. /**
  332. * Which {@code VerificationMode} to use during verification.
  333. * @private
  334. */
  335. this.verificationMode_ = goog.labs.mock.verification.atLeast(1);
  336. };
  337. /**
  338. * Allows callers of {@code #verify} to override the default verification
  339. * mode of this MockManager.
  340. *
  341. * @param {!goog.labs.mock.verification.VerificationMode} verificationMode
  342. * @private
  343. */
  344. goog.labs.mock.MockManager_.prototype.setVerificationMode_ = function(
  345. verificationMode) {
  346. this.verificationMode_ = verificationMode;
  347. };
  348. /**
  349. * Handles the first step in creating a stub, returning a stub-binder that
  350. * is later used to bind a stub for a method.
  351. *
  352. * @param {string} methodName The name of the method being bound.
  353. * @param {...*} var_args The arguments to the method.
  354. * @return {!goog.labs.mock.StubBinder} The stub binder.
  355. * @private
  356. */
  357. goog.labs.mock.MockManager_.prototype.handleMockCall_ = function(
  358. methodName, var_args) {
  359. var args = goog.array.slice(arguments, 1);
  360. return new goog.labs.mock.StubBinderImpl_(this, methodName, args);
  361. };
  362. /**
  363. * Returns the mock object. This should have a stubbed method for each method
  364. * on the object being mocked.
  365. *
  366. * @return {!Object|!Function} The mock object.
  367. */
  368. goog.labs.mock.MockManager_.prototype.getMockedItem = function() {
  369. return this.mockedItem;
  370. };
  371. /**
  372. * Adds a binding for the method name and arguments to be stubbed.
  373. *
  374. * @param {?string} methodName The name of the stubbed method.
  375. * @param {!Array<?>} args The arguments passed to the method.
  376. * @param {!Function} func The stub function.
  377. * @return {!Array<?>} The array of stubs for further sequential stubs to be
  378. * appended.
  379. */
  380. goog.labs.mock.MockManager_.prototype.addBinding = function(
  381. methodName, args, func) {
  382. var binding = new goog.labs.mock.MethodBinding_(methodName, args, func);
  383. var sequentialStubsArray = [binding];
  384. goog.array.insertAt(this.methodBindings, sequentialStubsArray, 0);
  385. return sequentialStubsArray;
  386. };
  387. /**
  388. * Returns a stub, if defined, for the method name and arguments passed in.
  389. * If there are multiple stubs for this method name and arguments, then
  390. * the most recent binding will be used.
  391. *
  392. * If the next binding is a sequence of stubs, then they'll be returned
  393. * in order until only one is left, at which point it will be returned for every
  394. * subsequent call.
  395. *
  396. * @param {string} methodName The name of the stubbed method.
  397. * @param {!Array<?>} args The arguments passed to the method.
  398. * @return {?Function} The stub function or null.
  399. * @protected
  400. */
  401. goog.labs.mock.MockManager_.prototype.getNextBinding = function(
  402. methodName, args) {
  403. var bindings = goog.array.find(this.methodBindings, function(bindingArray) {
  404. return bindingArray[0].matches(
  405. methodName, args, false /* isVerification */);
  406. });
  407. if (bindings == null) {
  408. return null;
  409. }
  410. if (bindings.length > 1) {
  411. return bindings.shift().getStub();
  412. }
  413. return bindings[0].getStub();
  414. };
  415. /**
  416. * Returns a stub, if defined, for the method name and arguments passed in as
  417. * parameters.
  418. *
  419. * @param {string} methodName The name of the stubbed method.
  420. * @param {!Array<?>} args The arguments passed to the method.
  421. * @return {Function} The stub function or undefined.
  422. * @protected
  423. */
  424. goog.labs.mock.MockManager_.prototype.getExecutor = function(methodName, args) {
  425. return this.getNextBinding(methodName, args);
  426. };
  427. /**
  428. * Looks up the list of stubs defined on the mock object and executes the
  429. * function associated with that stub.
  430. *
  431. * @param {string} methodName The name of the method to execute.
  432. * @param {...*} var_args The arguments passed to the method.
  433. * @return {*} Value returned by the stub function.
  434. * @protected
  435. */
  436. goog.labs.mock.MockManager_.prototype.executeStub = function(
  437. methodName, var_args) {
  438. var args = goog.array.slice(arguments, 1);
  439. // Record this call
  440. this.recordCall_(methodName, args);
  441. var func = this.getExecutor(methodName, args);
  442. if (func) {
  443. return func.apply(null, args);
  444. }
  445. };
  446. /**
  447. * Records a call to 'methodName' with arguments 'args'.
  448. *
  449. * @param {string} methodName The name of the called method.
  450. * @param {!Array<?>} args The array of arguments.
  451. * @private
  452. */
  453. goog.labs.mock.MockManager_.prototype.recordCall_ = function(methodName, args) {
  454. var callRecord =
  455. new goog.labs.mock.MethodBinding_(methodName, args, goog.nullFunction);
  456. this.callRecords_.push(callRecord);
  457. };
  458. /**
  459. * Verify invocation of a method with specific arguments.
  460. *
  461. * @param {string} methodName The name of the method.
  462. * @param {...*} var_args The arguments passed.
  463. * @protected
  464. */
  465. goog.labs.mock.MockManager_.prototype.verifyInvocation = function(
  466. methodName, var_args) {
  467. var args = goog.array.slice(arguments, 1);
  468. var count = goog.array.count(this.callRecords_, function(binding) {
  469. return binding.matches(methodName, args, true /* isVerification */);
  470. });
  471. if (!this.verificationMode_.verify(count)) {
  472. throw new goog.labs.mock.VerificationError(
  473. this.callRecords_, methodName, this.verificationMode_, args);
  474. }
  475. };
  476. /**
  477. * Sets up mock for the given object (or class), stubbing out all the defined
  478. * methods. By default, all stubs return {@code undefined}, though stubs can be
  479. * later defined using {@code goog.labs.mock.when}.
  480. *
  481. * @param {!Object|!Function} objOrClass The object or class to set up the mock
  482. * for. A class is a constructor function.
  483. *
  484. * @constructor
  485. * @struct
  486. * @extends {goog.labs.mock.MockManager_}
  487. * @private
  488. */
  489. goog.labs.mock.MockObjectManager_ = function(objOrClass) {
  490. goog.labs.mock.MockObjectManager_.base(this, 'constructor');
  491. /**
  492. * Proxies the calls to establish the first step of the stub bindings (object
  493. * and method name)
  494. * @private
  495. */
  496. this.objectStubBinder_ = {};
  497. this.mockee = objOrClass;
  498. /**
  499. * The call verifier is used to verify the calls. It maps property names to
  500. * the method that does call verification.
  501. * @type {!Object<string, function(string, ...)>}
  502. * @private
  503. */
  504. this.objectCallVerifier_ = {};
  505. var obj;
  506. if (goog.isFunction(objOrClass)) {
  507. // Create a temporary subclass with a no-op constructor so that we can
  508. // create an instance and determine what methods it has.
  509. /**
  510. * @constructor
  511. * @final
  512. */
  513. var tempCtor = function() {};
  514. goog.inherits(tempCtor, objOrClass);
  515. obj = new tempCtor();
  516. } else {
  517. obj = objOrClass;
  518. }
  519. // Put the object being mocked in the prototype chain of the mock so that
  520. // it has all the correct properties and instanceof works.
  521. /**
  522. * @constructor
  523. * @final
  524. */
  525. var mockedItemCtor = function() {};
  526. mockedItemCtor.prototype = obj;
  527. this.mockedItem = new mockedItemCtor();
  528. var enumerableProperties = goog.object.getAllPropertyNames(obj);
  529. // The non enumerable properties are added due to the fact that IE8 does not
  530. // enumerate any of the prototype Object functions even when overriden and
  531. // mocking these is sometimes needed.
  532. for (var i = 0; i < goog.labs.mock.PROTOTYPE_FIELDS_.length; i++) {
  533. var prop = goog.labs.mock.PROTOTYPE_FIELDS_[i];
  534. if (!goog.array.contains(enumerableProperties, prop)) {
  535. enumerableProperties.push(prop);
  536. }
  537. }
  538. // Adds the properties to the mock, creating a proxy stub for each method on
  539. // the instance.
  540. for (var i = 0; i < enumerableProperties.length; i++) {
  541. var prop = enumerableProperties[i];
  542. if (goog.isFunction(obj[prop])) {
  543. this.mockedItem[prop] = goog.bind(this.executeStub, this, prop);
  544. // The stub binder used to create bindings.
  545. this.objectStubBinder_[prop] =
  546. goog.bind(this.handleMockCall_, this, prop);
  547. // The verifier verifies the calls.
  548. this.objectCallVerifier_[prop] =
  549. goog.bind(this.verifyInvocation, this, prop);
  550. }
  551. }
  552. // The alias for stub binder exposed to the world.
  553. this.mockedItem.$stubBinder = this.objectStubBinder_;
  554. // The alias for verifier for the world.
  555. this.mockedItem.$callVerifier = this.objectCallVerifier_;
  556. this.mockedItem.$verificationModeSetter =
  557. goog.bind(this.setVerificationMode_, this);
  558. };
  559. goog.inherits(goog.labs.mock.MockObjectManager_, goog.labs.mock.MockManager_);
  560. /**
  561. * Sets up the spying behavior for the given object.
  562. *
  563. * @param {!Object} obj The object to be spied on.
  564. *
  565. * @constructor
  566. * @struct
  567. * @extends {goog.labs.mock.MockObjectManager_}
  568. * @private
  569. */
  570. goog.labs.mock.MockSpyManager_ = function(obj) {
  571. goog.labs.mock.MockSpyManager_.base(this, 'constructor', obj);
  572. };
  573. goog.inherits(
  574. goog.labs.mock.MockSpyManager_, goog.labs.mock.MockObjectManager_);
  575. /**
  576. * Return a stub, if defined, for the method and arguments passed in. If we lack
  577. * a stub, instead look for a call record that matches the method and arguments.
  578. *
  579. * @return {!Function} The stub or the invocation logger, if defined.
  580. * @override
  581. */
  582. goog.labs.mock.MockSpyManager_.prototype.getNextBinding = function(
  583. methodName, args) {
  584. var stub = goog.labs.mock.MockSpyManager_.base(
  585. this, 'getNextBinding', methodName, args);
  586. if (!stub) {
  587. stub = goog.bind(this.mockee[methodName], this.mockee);
  588. }
  589. return stub;
  590. };
  591. /**
  592. * Sets up mock for the given function, stubbing out. By default, all stubs
  593. * return {@code undefined}, though stubs can be later defined using
  594. * {@code goog.labs.mock.when}.
  595. *
  596. * @param {!Function} func The function to set up the mock for.
  597. *
  598. * @constructor
  599. * @struct
  600. * @extends {goog.labs.mock.MockManager_}
  601. * @private
  602. */
  603. goog.labs.mock.MockFunctionManager_ = function(func) {
  604. goog.labs.mock.MockFunctionManager_.base(this, 'constructor');
  605. this.func_ = func;
  606. /**
  607. * The stub binder used to create bindings.
  608. * Sets the first argument of handleMockCall_ to the function name.
  609. * @type {!Function}
  610. * @private
  611. */
  612. this.functionStubBinder_ = this.useMockedFunctionName_(this.handleMockCall_);
  613. this.mockedItem = this.useMockedFunctionName_(this.executeStub);
  614. this.mockedItem.$stubBinder = this.functionStubBinder_;
  615. /**
  616. * The call verifier is used to verify function invocations.
  617. * Sets the first argument of verifyInvocation to the function name.
  618. * @type {!Function}
  619. */
  620. this.mockedItem.$callVerifier =
  621. this.useMockedFunctionName_(this.verifyInvocation);
  622. // This has to be repeated because if it's set in base class it will be
  623. // stubbed by MockObjectManager.
  624. this.mockedItem.$verificationModeSetter =
  625. goog.bind(this.setVerificationMode_, this);
  626. };
  627. goog.inherits(goog.labs.mock.MockFunctionManager_, goog.labs.mock.MockManager_);
  628. /**
  629. * Given a method, returns a new function that calls the first one setting
  630. * the first argument to the mocked function name.
  631. * This is used to dynamically override the stub binders and call verifiers.
  632. * @private
  633. * @param {Function} nextFunc The function to override.
  634. * @return {!Function} The overloaded function.
  635. */
  636. goog.labs.mock.MockFunctionManager_.prototype.useMockedFunctionName_ = function(
  637. nextFunc) {
  638. var mockFunctionManager = this;
  639. // Avoid using 'this' because this function may be called with 'new'.
  640. return function(var_args) {
  641. var args = goog.array.clone(arguments);
  642. var name = '#mockFor<' +
  643. goog.labs.mock.getFunctionName_(mockFunctionManager.func_) + '>';
  644. goog.array.insertAt(args, name, 0);
  645. return nextFunc.apply(mockFunctionManager, args);
  646. };
  647. };
  648. /**
  649. * A stub binder is an object that helps define the stub by binding
  650. * method name to the stub method.
  651. * @interface
  652. */
  653. goog.labs.mock.StubBinder = function() {};
  654. /**
  655. * Defines the function to be called for the method name and arguments bound
  656. * to this {@code StubBinder}.
  657. *
  658. * If {@code then} or {@code thenReturn} has been previously called
  659. * on this {@code StubBinder} then the given stub {@code func} will be called
  660. * only after the stubs passed previously have been called. Afterwards,
  661. * if no other calls are made to {@code then} or {@code thenReturn} for this
  662. * {@code StubBinder} then the given {@code func} will be used for every further
  663. * invocation.
  664. * See #when for complete examples.
  665. * TODO(user): Add support for the 'Answer' interface.
  666. *
  667. * @param {!Function} func The function to call.
  668. * @return {!goog.labs.mock.StubBinder} Returns itself for chaining.
  669. */
  670. goog.labs.mock.StubBinder.prototype.then = goog.abstractMethod;
  671. /**
  672. * Defines the constant return value for the stub represented by this
  673. * {@code StubBinder}.
  674. *
  675. * @param {*} value The value to return.
  676. * @return {!goog.labs.mock.StubBinder} Returns itself for chaining.
  677. */
  678. goog.labs.mock.StubBinder.prototype.thenReturn = goog.abstractMethod;
  679. /**
  680. * A {@code StubBinder} which uses {@code MockManager_} to manage stub
  681. * bindings.
  682. *
  683. * @param {!goog.labs.mock.MockManager_}
  684. * mockManager The mock manager.
  685. * @param {?string} name The method name.
  686. * @param {!Array<?>} args The other arguments to the method.
  687. *
  688. * @implements {goog.labs.mock.StubBinder}
  689. * @private @constructor @struct @final
  690. */
  691. goog.labs.mock.StubBinderImpl_ = function(mockManager, name, args) {
  692. /**
  693. * The mock manager instance.
  694. * @type {!goog.labs.mock.MockManager_}
  695. * @private
  696. */
  697. this.mockManager_ = mockManager;
  698. /**
  699. * Holds the name of the method to be bound.
  700. * @type {?string}
  701. * @private
  702. */
  703. this.name_ = name;
  704. /**
  705. * Holds the arguments for the method.
  706. * @type {!Array<?>}
  707. * @private
  708. */
  709. this.args_ = args;
  710. /**
  711. * Stores a reference to the list of stubs to allow chaining sequential
  712. * stubs.
  713. * @private {!Array<?>}
  714. */
  715. this.sequentialStubsArray_ = [];
  716. };
  717. /**
  718. * @override
  719. */
  720. goog.labs.mock.StubBinderImpl_.prototype.then = function(func) {
  721. if (this.sequentialStubsArray_.length) {
  722. this.sequentialStubsArray_.push(
  723. new goog.labs.mock.MethodBinding_(this.name_, this.args_, func));
  724. } else {
  725. this.sequentialStubsArray_ =
  726. this.mockManager_.addBinding(this.name_, this.args_, func);
  727. }
  728. return this;
  729. };
  730. /**
  731. * @override
  732. */
  733. goog.labs.mock.StubBinderImpl_.prototype.thenReturn = function(value) {
  734. return this.then(goog.functions.constant(value));
  735. };
  736. /**
  737. * Facilitates (and is the first step in) setting up stubs. Obtains an object
  738. * on which, the method to be mocked is called to create a stub. Sample usage:
  739. *
  740. * var mockObj = goog.labs.mock.mock(objectBeingMocked);
  741. * goog.labs.mock.when(mockObj).getFoo(3).thenReturn(4);
  742. *
  743. * Subsequent calls to {@code when} take precedence over earlier calls, allowing
  744. * users to set up default stubs in setUp methods and then override them in
  745. * individual tests.
  746. *
  747. * If a user wants sequential calls to their stub to return different
  748. * values, they can chain calls to {@code then} or {@code thenReturn} as
  749. * follows:
  750. *
  751. * var mockObj = goog.labs.mock.mock(objectBeingMocked);
  752. * goog.labs.mock.when(mockObj).getFoo(3)
  753. * .thenReturn(4)
  754. * .then(function() {
  755. * throw Error('exceptional case');
  756. * });
  757. *
  758. * @param {!Object} mockObject The mocked object.
  759. * @return {!goog.labs.mock.StubBinder} The property binder.
  760. */
  761. goog.labs.mock.when = function(mockObject) {
  762. goog.asserts.assert(mockObject.$stubBinder, 'Stub binder cannot be null!');
  763. return mockObject.$stubBinder;
  764. };
  765. /**
  766. * Represents a binding between a method name, args and a stub.
  767. *
  768. * @param {?string} methodName The name of the method being stubbed.
  769. * @param {!Array<?>} args The arguments passed to the method.
  770. * @param {!Function} stub The stub function to be called for the given method.
  771. * @constructor
  772. * @struct
  773. * @private
  774. */
  775. goog.labs.mock.MethodBinding_ = function(methodName, args, stub) {
  776. /**
  777. * The name of the method being stubbed.
  778. * @type {?string}
  779. * @private
  780. */
  781. this.methodName_ = methodName;
  782. /**
  783. * The arguments for the method being stubbed.
  784. * @type {!Array<?>}
  785. * @private
  786. */
  787. this.args_ = args;
  788. /**
  789. * The stub function.
  790. * @type {!Function}
  791. * @private
  792. */
  793. this.stub_ = stub;
  794. };
  795. /**
  796. * @return {!Function} The stub to be executed.
  797. */
  798. goog.labs.mock.MethodBinding_.prototype.getStub = function() {
  799. return this.stub_;
  800. };
  801. /**
  802. * @override
  803. * @return {string} A readable string representation of the binding
  804. * as a method call.
  805. */
  806. goog.labs.mock.MethodBinding_.prototype.toString = function() {
  807. return goog.labs.mock.formatMethodCall_(this.methodName_ || '', this.args_);
  808. };
  809. /**
  810. * @return {string} The method name for this binding.
  811. */
  812. goog.labs.mock.MethodBinding_.prototype.getMethodName = function() {
  813. return this.methodName_ || '';
  814. };
  815. /**
  816. * Determines whether the given args match the stored args_. Used to determine
  817. * which stub to invoke for a method.
  818. *
  819. * @param {string} methodName The name of the method being stubbed.
  820. * @param {!Array<?>} args An array of arguments.
  821. * @param {boolean} isVerification Whether this is a function verification call
  822. * or not.
  823. * @return {boolean} If it matches the stored arguments.
  824. */
  825. goog.labs.mock.MethodBinding_.prototype.matches = function(
  826. methodName, args, isVerification) {
  827. var specs = isVerification ? args : this.args_;
  828. var calls = isVerification ? this.args_ : args;
  829. // TODO(user): More elaborate argument matching. Think about matching
  830. // objects.
  831. return this.methodName_ == methodName &&
  832. goog.array.equals(calls, specs, function(arg, spec) {
  833. // Duck-type to see if this is an object that implements the
  834. // goog.labs.testing.Matcher interface.
  835. if (spec && goog.isFunction(spec.matches)) {
  836. return spec.matches(arg);
  837. } else {
  838. return goog.array.defaultCompareEquality(spec, arg);
  839. }
  840. });
  841. };