recordfunction.js 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212
  1. // Copyright 2010 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 Helper class for recording the calls of a function.
  16. *
  17. * Example:
  18. * <pre>
  19. * var stubs = new goog.testing.PropertyReplacer();
  20. *
  21. * function tearDown() {
  22. * stubs.reset();
  23. * }
  24. *
  25. * function testShuffle() {
  26. * stubs.set(Math, 'random', goog.testing.recordFunction(Math.random));
  27. * var arr = shuffle([1, 2, 3, 4, 5]);
  28. * assertSameElements([1, 2, 3, 4, 5], arr);
  29. * assertEquals(4, Math.random.getCallCount());
  30. * }
  31. *
  32. * function testOpenDialog() {
  33. * stubs.set(goog.ui, 'Dialog',
  34. * goog.testing.recordConstructor(goog.ui.Dialog));
  35. * openConfirmDialog();
  36. * var lastDialogInstance = goog.ui.Dialog.getLastCall().getThis();
  37. * assertEquals('confirm', lastDialogInstance.getTitle());
  38. * }
  39. * </pre>
  40. *
  41. */
  42. goog.setTestOnly('goog.testing.FunctionCall');
  43. goog.provide('goog.testing.FunctionCall');
  44. goog.provide('goog.testing.recordConstructor');
  45. goog.provide('goog.testing.recordFunction');
  46. goog.require('goog.testing.asserts');
  47. /**
  48. * Wraps the function into another one which calls the inner function and
  49. * records its calls. The recorded function will have 3 static methods:
  50. * {@code getCallCount}, {@code getCalls} and {@code getLastCall} but won't
  51. * inherit the original function's prototype and static fields.
  52. *
  53. * @param {!Function=} opt_f The function to wrap and record. Defaults to
  54. * {@link goog.nullFunction}.
  55. * @return {!Function} The wrapped function.
  56. */
  57. goog.testing.recordFunction = function(opt_f) {
  58. var f = opt_f || goog.nullFunction;
  59. var calls = [];
  60. function recordedFunction() {
  61. try {
  62. var ret = f.apply(this, arguments);
  63. calls.push(new goog.testing.FunctionCall(f, this, arguments, ret, null));
  64. return ret;
  65. } catch (err) {
  66. calls.push(
  67. new goog.testing.FunctionCall(f, this, arguments, undefined, err));
  68. throw err;
  69. }
  70. }
  71. /**
  72. * @return {number} Total number of calls.
  73. */
  74. recordedFunction.getCallCount = function() { return calls.length; };
  75. /**
  76. * Asserts that the function was called a certain number of times.
  77. * @param {number|string} a The expected number of calls (1 arg) or debug
  78. * message (2 args).
  79. * @param {number=} opt_b The expected number of calls (2 args only).
  80. */
  81. recordedFunction.assertCallCount = function(a, opt_b) {
  82. var actual = calls.length;
  83. var expected = arguments.length == 1 ? a : opt_b;
  84. var message = arguments.length == 1 ? '' : ' ' + a;
  85. assertEquals(
  86. 'Expected ' + expected + ' call(s), but was ' + actual + '.' + message,
  87. expected, actual);
  88. };
  89. /**
  90. * @return {!Array<!goog.testing.FunctionCall>} All calls of the recorded
  91. * function.
  92. */
  93. recordedFunction.getCalls = function() { return calls; };
  94. /**
  95. * @return {goog.testing.FunctionCall} Last call of the recorded function or
  96. * null if it hasn't been called.
  97. */
  98. recordedFunction.getLastCall = function() {
  99. return calls[calls.length - 1] || null;
  100. };
  101. /**
  102. * Returns and removes the last call of the recorded function.
  103. * @return {goog.testing.FunctionCall} Last call of the recorded function or
  104. * null if it hasn't been called.
  105. */
  106. recordedFunction.popLastCall = function() { return calls.pop() || null; };
  107. /**
  108. * Resets the recorded function and removes all calls.
  109. */
  110. recordedFunction.reset = function() { calls.length = 0; };
  111. return recordedFunction;
  112. };
  113. /**
  114. * Same as {@link goog.testing.recordFunction} but the recorded function will
  115. * have the same prototype and static fields as the original one. It can be
  116. * used with constructors.
  117. *
  118. * @param {!Function} ctor The function to wrap and record.
  119. * @return {!Function} The wrapped function.
  120. */
  121. goog.testing.recordConstructor = function(ctor) {
  122. var recordedConstructor = goog.testing.recordFunction(ctor);
  123. recordedConstructor.prototype = ctor.prototype;
  124. goog.mixin(recordedConstructor, ctor);
  125. return recordedConstructor;
  126. };
  127. /**
  128. * Struct for a single function call.
  129. * @param {!Function} func The called function.
  130. * @param {!Object} thisContext {@code this} context of called function.
  131. * @param {!Arguments} args Arguments of the called function.
  132. * @param {*} ret Return value of the function or undefined in case of error.
  133. * @param {*} error The error thrown by the function or null if none.
  134. * @constructor
  135. */
  136. goog.testing.FunctionCall = function(func, thisContext, args, ret, error) {
  137. this.function_ = func;
  138. this.thisContext_ = thisContext;
  139. this.arguments_ = Array.prototype.slice.call(args);
  140. this.returnValue_ = ret;
  141. this.error_ = error;
  142. };
  143. /**
  144. * @return {!Function} The called function.
  145. */
  146. goog.testing.FunctionCall.prototype.getFunction = function() {
  147. return this.function_;
  148. };
  149. /**
  150. * @return {!Object} {@code this} context of called function. It is the same as
  151. * the created object if the function is a constructor.
  152. */
  153. goog.testing.FunctionCall.prototype.getThis = function() {
  154. return this.thisContext_;
  155. };
  156. /**
  157. * @return {!Array<?>} Arguments of the called function.
  158. */
  159. goog.testing.FunctionCall.prototype.getArguments = function() {
  160. return this.arguments_;
  161. };
  162. /**
  163. * Returns the nth argument of the called function.
  164. * @param {number} index 0-based index of the argument.
  165. * @return {*} The argument value or undefined if there is no such argument.
  166. */
  167. goog.testing.FunctionCall.prototype.getArgument = function(index) {
  168. return this.arguments_[index];
  169. };
  170. /**
  171. * @return {*} Return value of the function or undefined in case of error.
  172. */
  173. goog.testing.FunctionCall.prototype.getReturnValue = function() {
  174. return this.returnValue_;
  175. };
  176. /**
  177. * @return {*} The error thrown by the function or null if none.
  178. */
  179. goog.testing.FunctionCall.prototype.getError = function() {
  180. return this.error_;
  181. };