# 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 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 | /**
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 | */
43 |
44 | goog.provide('goog.testing.FunctionCall');
45 | goog.provide('goog.testing.recordConstructor');
46 | goog.provide('goog.testing.recordFunction');
47 |
48 | goog.require('goog.testing.asserts');
49 |
50 |
51 | /**
52 | * Wraps the function into another one which calls the inner function and
53 | * records its calls. The recorded function will have 3 static methods:
54 | * {@code getCallCount}, {@code getCalls} and {@code getLastCall} but won't
55 | * inherit the original function's prototype and static fields.
56 | *
57 | * @param {!Function=} opt_f The function to wrap and record. Defaults to
58 | * {@link goog.nullFunction}.
59 | * @return {!Function} The wrapped function.
60 | */
61 | goog.testing.recordFunction = function(opt_f) {
62 | var f = opt_f || goog.nullFunction;
63 | var calls = [];
64 |
65 | 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 | }
76 |
77 | /**
78 | * @return {number} Total number of calls.
79 | */
80 | recordedFunction.getCallCount = function() {
81 | return calls.length;
82 | };
83 |
84 | /**
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 | };
94 |
95 | /**
96 | * @return {!Array<!goog.testing.FunctionCall>} All calls of the recorded
97 | * function.
98 | */
99 | recordedFunction.getCalls = function() {
100 | return calls;
101 | };
102 |
103 |
104 | /**
105 | * @return {goog.testing.FunctionCall} Last call of the recorded function or
106 | * null if it hasn't been called.
107 | */
108 | recordedFunction.getLastCall = function() {
109 | return calls[calls.length - 1] || null;
110 | };
111 |
112 | /**
113 | * Returns and removes the last call of the recorded function.
114 | * @return {goog.testing.FunctionCall} Last call of the recorded function or
115 | * null if it hasn't been called.
116 | */
117 | recordedFunction.popLastCall = function() {
118 | return calls.pop() || null;
119 | };
120 |
121 | /**
122 | * Resets the recorded function and removes all calls.
123 | */
124 | recordedFunction.reset = function() {
125 | calls.length = 0;
126 | };
127 |
128 | return recordedFunction;
129 | };
130 |
131 |
132 | /**
133 | * Same as {@link goog.testing.recordFunction} but the recorded function will
134 | * have the same prototype and static fields as the original one. It can be
135 | * used with constructors.
136 | *
137 | * @param {!Function} ctor The function to wrap and record.
138 | * @return {!Function} The wrapped function.
139 | */
140 | goog.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 | };
146 |
147 |
148 |
149 | /**
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 | * @constructor
157 | */
158 | goog.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 | };
165 |
166 |
167 | /**
168 | * @return {!Function} The called function.
169 | */
170 | goog.testing.FunctionCall.prototype.getFunction = function() {
171 | return this.function_;
172 | };
173 |
174 |
175 | /**
176 | * @return {!Object} {@code this} context of called function. It is the same as
177 | * the created object if the function is a constructor.
178 | */
179 | goog.testing.FunctionCall.prototype.getThis = function() {
180 | return this.thisContext_;
181 | };
182 |
183 |
184 | /**
185 | * @return {!Array<?>} Arguments of the called function.
186 | */
187 | goog.testing.FunctionCall.prototype.getArguments = function() {
188 | return this.arguments_;
189 | };
190 |
191 |
192 | /**
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 | */
197 | goog.testing.FunctionCall.prototype.getArgument = function(index) {
198 | return this.arguments_[index];
199 | };
200 |
201 |
202 | /**
203 | * @return {*} Return value of the function or undefined in case of error.
204 | */
205 | goog.testing.FunctionCall.prototype.getReturnValue = function() {
206 | return this.returnValue_;
207 | };
208 |
209 |
210 | /**
211 | * @return {*} The error thrown by the function or null if none.
212 | */
213 | goog.testing.FunctionCall.prototype.getError = function() {
214 | return this.error_;
215 | };