Path: blob/trunk/third_party/closure/goog/testing/recordfunction.js
4503 views
/**1* @license2* Copyright The Closure Library Authors.3* SPDX-License-Identifier: Apache-2.04*/56/**7* @fileoverview Helper class for recording the calls of a function.8*9* Example:10* <pre>11* var stubs = new goog.testing.PropertyReplacer();12*13* function tearDown() {14* stubs.reset();15* }16*17* function testShuffle() {18* stubs.replace(Math, 'random', goog.testing.recordFunction(Math.random));19* var arr = shuffle([1, 2, 3, 4, 5]);20* assertSameElements([1, 2, 3, 4, 5], arr);21* assertEquals(4, Math.random.getCallCount());22* }23*24* function testOpenDialog() {25* stubs.replace(goog.ui, 'Dialog',26* goog.testing.recordConstructor(goog.ui.Dialog));27* openConfirmDialog();28* var lastDialogInstance = goog.ui.Dialog.getLastCall().getThis();29* assertEquals('confirm', lastDialogInstance.getTitle());30* }31* </pre>32*/3334goog.setTestOnly('goog.testing.FunctionCall');35goog.provide('goog.testing.FunctionCall');36goog.provide('goog.testing.recordConstructor');37goog.provide('goog.testing.recordFunction');3839goog.require('goog.Promise');40goog.require('goog.functions');41goog.require('goog.promise.Resolver');42goog.require('goog.testing.asserts');434445/**46* A function that represents the return type of recordFunction.47* @private48* @param {...?} var_args49* @return {?}50*/51goog.testing.recordedFunction_ = function(var_args) {};5253/**54* @return {number} Total number of calls.55*/56goog.testing.recordedFunction_.getCallCount = function() {};5758/**59* Asserts that the function was called a certain number of times.60* @param {number|string} a The expected number of calls (1 arg) or debug61* message (2 args).62* @param {number=} opt_b The expected number of calls (2 args only).63*/64goog.testing.recordedFunction_.assertCallCount = function(a, opt_b) {};6566/**67* @return {!Array<!goog.testing.FunctionCall>} All calls of the recorded68* function.69*/70goog.testing.recordedFunction_.getCalls = function() {};7172/**73* @return {?goog.testing.FunctionCall} Last call of the recorded function or74* null if it hasn't been called.75*/76goog.testing.recordedFunction_.getLastCall = function() {};7778/**79* Returns and removes the last call of the recorded function.80* @return {?goog.testing.FunctionCall} Last call of the recorded function or81* null if it hasn't been called.82*/83goog.testing.recordedFunction_.popLastCall = function() {};8485/**86* Returns a goog.Promise that resolves when the recorded function has equal87* to or greater than the number of calls.88* @param {number} num89* @return {!goog.Promise<undefined>}90*/91goog.testing.recordedFunction_.waitForCalls = function(num) {};9293/**94* Resets the recorded function and removes all calls.95* @return {void}96*/97goog.testing.recordedFunction_.reset = function() {};9899/**100* Wraps the function into another one which calls the inner function and101* records its calls. The recorded function will have 3 static methods:102* `getCallCount`, `getCalls` and `getLastCall` but won't103* inherit the original function's prototype and static fields.104*105* @param {!Function=} opt_f The function to wrap and record. Defaults to106* goog.functions.UNDEFINED.107* @return {!goog.testing.recordFunction.Type} The wrapped function.108*/109goog.testing.recordFunction = function(opt_f) {110'use strict';111var f = opt_f || goog.functions.UNDEFINED;112var calls = [];113/** @type {?goog.promise.Resolver} */114var waitForCallsResolver = null;115/** @type {number} */116var waitForCallsCount = 0;117118function maybeResolveWaitForCalls() {119if (waitForCallsResolver && calls.length >= waitForCallsCount) {120waitForCallsResolver.resolve();121waitForCallsResolver = null;122waitForCallsCount = 0;123}124}125126/** @type {!goog.testing.recordFunction.Type} */127function recordedFunction() {128var owner = /** @type {?} */ (this);129try {130var ret = f.apply(owner, arguments);131calls.push(new goog.testing.FunctionCall(f, owner, arguments, ret, null));132maybeResolveWaitForCalls();133return ret;134} catch (err) {135calls.push(136new goog.testing.FunctionCall(f, owner, arguments, undefined, err));137maybeResolveWaitForCalls();138throw err;139}140}141142/**143* @return {number} Total number of calls.144*/145recordedFunction.getCallCount = function() {146'use strict';147return calls.length;148};149150/**151* Asserts that the function was called a certain number of times.152* @param {number|string} a The expected number of calls (1 arg) or debug153* message (2 args).154* @param {number=} opt_b The expected number of calls (2 args only).155*/156recordedFunction.assertCallCount = function(a, opt_b) {157'use strict';158var actual = calls.length;159var expected = arguments.length == 1 ? a : opt_b;160var message = arguments.length == 1 ? '' : ' ' + a;161assertEquals(162'Expected ' + expected + ' call(s), but was ' + actual + '.' + message,163expected, actual);164};165166/**167* @return {!Array<!goog.testing.FunctionCall>} All calls of the recorded168* function.169*/170recordedFunction.getCalls = function() {171'use strict';172return calls;173};174175176/**177* @return {goog.testing.FunctionCall} Last call of the recorded function or178* null if it hasn't been called.179*/180recordedFunction.getLastCall = function() {181'use strict';182return calls[calls.length - 1] || null;183};184185/**186* Returns and removes the last call of the recorded function.187* @return {goog.testing.FunctionCall} Last call of the recorded function or188* null if it hasn't been called.189*/190recordedFunction.popLastCall = function() {191'use strict';192return calls.pop() || null;193};194195/**196* Returns a goog.Promise that resolves when the recorded function has equal197* to or greater than the number of calls.198* @param {number} num199* @return {!goog.Promise<undefined>}200*/201recordedFunction.waitForCalls = function(num) {202'use strict';203waitForCallsCount = num;204waitForCallsResolver = goog.Promise.withResolver();205var promise = waitForCallsResolver.promise;206maybeResolveWaitForCalls();207return promise;208};209210/**211* Resets the recorded function and removes all calls.212*/213recordedFunction.reset = function() {214'use strict';215calls.length = 0;216waitForCallsResolver = null;217waitForCallsCount = 0;218};219220return recordedFunction;221};222223/** @typedef {typeof goog.testing.recordedFunction_} */224goog.testing.recordFunction.Type;225226227/**228* Same as {@link goog.testing.recordFunction} but the recorded function will229* have the same prototype and static fields as the original one. It can be230* used with constructors.231*232* @param {!Function} ctor The function to wrap and record.233* @return {!Function} The wrapped function.234*/235goog.testing.recordConstructor = function(ctor) {236'use strict';237var recordedConstructor = goog.testing.recordFunction(ctor);238recordedConstructor.prototype = ctor.prototype;239240// NOTE: This does not handle non-enumerable properties, should it?241for (var x in ctor) {242recordedConstructor[x] = ctor[x];243}244return recordedConstructor;245};246247248249/**250* Struct for a single function call.251* @param {!Function} func The called function.252* @param {!Object} thisContext `this` context of called function.253* @param {!Arguments} args Arguments of the called function.254* @param {*} ret Return value of the function or undefined in case of error.255* @param {*} error The error thrown by the function or null if none.256* @constructor257*/258goog.testing.FunctionCall = function(func, thisContext, args, ret, error) {259'use strict';260this.function_ = func;261this.thisContext_ = thisContext;262this.arguments_ = Array.prototype.slice.call(args);263this.returnValue_ = ret;264this.error_ = error;265};266267268/**269* @return {!Function} The called function.270*/271goog.testing.FunctionCall.prototype.getFunction = function() {272'use strict';273return this.function_;274};275276277/**278* @return {!Object} `this` context of called function. It is the same as279* the created object if the function is a constructor.280*/281goog.testing.FunctionCall.prototype.getThis = function() {282'use strict';283return this.thisContext_;284};285286287/**288* @return {!Array<?>} Arguments of the called function.289*/290goog.testing.FunctionCall.prototype.getArguments = function() {291'use strict';292return this.arguments_;293};294295296/**297* Returns the nth argument of the called function.298* @param {number} index 0-based index of the argument.299* @return {*} The argument value or undefined if there is no such argument.300*/301goog.testing.FunctionCall.prototype.getArgument = function(index) {302'use strict';303return this.arguments_[index];304};305306307/**308* @return {*} Return value of the function or undefined in case of error.309*/310goog.testing.FunctionCall.prototype.getReturnValue = function() {311'use strict';312return this.returnValue_;313};314315316/**317* @return {*} The error thrown by the function or null if none.318*/319goog.testing.FunctionCall.prototype.getError = function() {320'use strict';321return this.error_;322};323324325