# lib/goog/testing/recordfunction.js

    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 at6//7//      http://www.apache.org/licenses/LICENSE-2.08//9// Unless required by applicable law or agreed to in writing, software10// 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 and13// limitations under the License.1415/**16 * @fileoverview Helper class for recording the calls of a function.17 *18 * Example:19 * <pre>20 * var stubs = new goog.testing.PropertyReplacer();21 *22 * function tearDown() {23 *   stubs.reset();24 * }25 *26 * function testShuffle() {27 *   stubs.set(Math, 'random', goog.testing.recordFunction(Math.random));28 *   var arr = shuffle([1, 2, 3, 4, 5]);29 *   assertSameElements([1, 2, 3, 4, 5], arr);30 *   assertEquals(4, Math.random.getCallCount());31 * }32 *33 * function testOpenDialog() {34 *   stubs.set(goog.ui, 'Dialog',35 *       goog.testing.recordConstructor(goog.ui.Dialog));36 *   openConfirmDialog();37 *   var lastDialogInstance = goog.ui.Dialog.getLastCall().getThis();38 *   assertEquals('confirm', lastDialogInstance.getTitle());39 * }40 * </pre>41 *42 */4344goog.provide('goog.testing.FunctionCall');45goog.provide('goog.testing.recordConstructor');46goog.provide('goog.testing.recordFunction');4748goog.require('goog.testing.asserts');495051/**52 * Wraps the function into another one which calls the inner function and53 * records its calls. The recorded function will have 3 static methods:54 * {@code getCallCount}, {@code getCalls} and {@code getLastCall} but won't55 * inherit the original function's prototype and static fields.56 *57 * @param {!Function=} opt_f The function to wrap and record. Defaults to58 *     {@link goog.nullFunction}.59 * @return {!Function} The wrapped function.60 */61goog.testing.recordFunction = function(opt_f) {62  var f = opt_f || goog.nullFunction;63  var calls = [];6465  function recordedFunction() {66    try {67      var ret = f.apply(this, arguments);68      calls.push(new goog.testing.FunctionCall(f, this, arguments, ret, null));69      return ret;70    } catch (err) {71      calls.push(new goog.testing.FunctionCall(f, this, arguments, undefined,72          err));73      throw err;74    }75  }7677  /**78   * @return {number} Total number of calls.79   */80  recordedFunction.getCallCount = function() {81    return calls.length;82  };8384  /**85   * Asserts that the function was called {@code expected} times.86   * @param {number} expected The expected number of calls.87   */88  recordedFunction.assertCallCount = function(expected) {89    var actual = calls.length;90    assertEquals(91        'Expected ' + expected + ' call(s), but was ' + actual + '.',92        expected, actual);93  };9495  /**96   * @return {!Array.<!goog.testing.FunctionCall>} All calls of the recorded97   *     function.98   */99  recordedFunction.getCalls = function() {100    return calls;101  };102103104  /**105   * @return {goog.testing.FunctionCall} Last call of the recorded function or106   *     null if it hasn't been called.107   */108  recordedFunction.getLastCall = function() {109    return calls[calls.length - 1] || null;110  };111112  /**113   * Returns and removes the last call of the recorded function.114   * @return {goog.testing.FunctionCall} Last call of the recorded function or115   *     null if it hasn't been called.116   */117  recordedFunction.popLastCall = function() {118    return calls.pop() || null;119  };120121  /**122   * Resets the recorded function and removes all calls.123   */124  recordedFunction.reset = function() {125    calls.length = 0;126  };127128  return recordedFunction;129};130131132/**133 * Same as {@link goog.testing.recordFunction} but the recorded function will134 * have the same prototype and static fields as the original one. It can be135 * used with constructors.136 *137 * @param {!Function} ctor The function to wrap and record.138 * @return {!Function} The wrapped function.139 */140goog.testing.recordConstructor = function(ctor) {141  var recordedConstructor = goog.testing.recordFunction(ctor);142  recordedConstructor.prototype = ctor.prototype;143  goog.mixin(recordedConstructor, ctor);144  return recordedConstructor;145};146147148149/**150 * Struct for a single function call.151 * @param {!Function} func The called function.152 * @param {!Object} thisContext {@code this} context of called function.153 * @param {!Arguments} args Arguments of the called function.154 * @param {*} ret Return value of the function or undefined in case of error.155 * @param {*} error The error thrown by the function or null if none.156 * @constructor157 */158goog.testing.FunctionCall = function(func, thisContext, args, ret, error) {159  this.function_ = func;160  this.thisContext_ = thisContext;161  this.arguments_ = Array.prototype.slice.call(args);162  this.returnValue_ = ret;163  this.error_ = error;164};165166167/**168 * @return {!Function} The called function.169 */170goog.testing.FunctionCall.prototype.getFunction = function() {171  return this.function_;172};173174175/**176 * @return {!Object} {@code this} context of called function. It is the same as177 *     the created object if the function is a constructor.178 */179goog.testing.FunctionCall.prototype.getThis = function() {180  return this.thisContext_;181};182183184/**185 * @return {!Array} Arguments of the called function.186 */187goog.testing.FunctionCall.prototype.getArguments = function() {188  return this.arguments_;189};190191192/**193 * Returns the nth argument of the called function.194 * @param {number} index 0-based index of the argument.195 * @return {*} The argument value or undefined if there is no such argument.196 */197goog.testing.FunctionCall.prototype.getArgument = function(index) {198  return this.arguments_[index];199};200201202/**203 * @return {*} Return value of the function or undefined in case of error.204 */205goog.testing.FunctionCall.prototype.getReturnValue = function() {206  return this.returnValue_;207};208209210/**211 * @return {*} The error thrown by the function or null if none.212 */213goog.testing.FunctionCall.prototype.getError = function() {214  return this.error_;215};