1 | // Copyright 2008 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 Matchers to be used with the mock utilities. They allow for |
17 | * flexible matching by type. Custom matchers can be created by passing a |
18 | * matcher function into an ArgumentMatcher instance. |
19 | * |
20 | * For examples, please see the unit test. |
21 | * |
22 | */ |
23 | |
24 | |
25 | goog.provide('goog.testing.mockmatchers'); |
26 | goog.provide('goog.testing.mockmatchers.ArgumentMatcher'); |
27 | goog.provide('goog.testing.mockmatchers.IgnoreArgument'); |
28 | goog.provide('goog.testing.mockmatchers.InstanceOf'); |
29 | goog.provide('goog.testing.mockmatchers.ObjectEquals'); |
30 | goog.provide('goog.testing.mockmatchers.RegexpMatch'); |
31 | goog.provide('goog.testing.mockmatchers.SaveArgument'); |
32 | goog.provide('goog.testing.mockmatchers.TypeOf'); |
33 | |
34 | goog.require('goog.array'); |
35 | goog.require('goog.dom'); |
36 | goog.require('goog.testing.asserts'); |
37 | |
38 | |
39 | |
40 | /** |
41 | * A simple interface for executing argument matching. A match in this case is |
42 | * testing to see if a supplied object fits a given criteria. True is returned |
43 | * if the given criteria is met. |
44 | * @param {Function=} opt_matchFn A function that evaluates a given argument |
45 | * and returns true if it meets a given criteria. |
46 | * @param {?string=} opt_matchName The name expressing intent as part of |
47 | * an error message for when a match fails. |
48 | * @constructor |
49 | */ |
50 | goog.testing.mockmatchers.ArgumentMatcher = |
51 | function(opt_matchFn, opt_matchName) { |
52 | /** |
53 | * A function that evaluates a given argument and returns true if it meets a |
54 | * given criteria. |
55 | * @type {Function} |
56 | * @private |
57 | */ |
58 | this.matchFn_ = opt_matchFn || null; |
59 | |
60 | /** |
61 | * A string indicating the match intent (e.g. isBoolean or isString). |
62 | * @type {?string} |
63 | * @private |
64 | */ |
65 | this.matchName_ = opt_matchName || null; |
66 | }; |
67 | |
68 | |
69 | /** |
70 | * A function that takes a match argument and an optional MockExpectation |
71 | * which (if provided) will get error information and returns whether or |
72 | * not it matches. |
73 | * @param {*} toVerify The argument that should be verified. |
74 | * @param {goog.testing.MockExpectation?=} opt_expectation The expectation |
75 | * for this match. |
76 | * @return {boolean} Whether or not a given argument passes verification. |
77 | */ |
78 | goog.testing.mockmatchers.ArgumentMatcher.prototype.matches = |
79 | function(toVerify, opt_expectation) { |
80 | if (this.matchFn_) { |
81 | var isamatch = this.matchFn_(toVerify); |
82 | if (!isamatch && opt_expectation) { |
83 | if (this.matchName_) { |
84 | opt_expectation.addErrorMessage('Expected: ' + |
85 | this.matchName_ + ' but was: ' + _displayStringForValue(toVerify)); |
86 | } else { |
87 | opt_expectation.addErrorMessage('Expected: missing mockmatcher' + |
88 | ' description but was: ' + |
89 | _displayStringForValue(toVerify)); |
90 | } |
91 | } |
92 | return isamatch; |
93 | } else { |
94 | throw Error('No match function defined for this mock matcher'); |
95 | } |
96 | }; |
97 | |
98 | |
99 | |
100 | /** |
101 | * A matcher that verifies that an argument is an instance of a given class. |
102 | * @param {Function} ctor The class that will be used for verification. |
103 | * @constructor |
104 | * @extends {goog.testing.mockmatchers.ArgumentMatcher} |
105 | * @final |
106 | */ |
107 | goog.testing.mockmatchers.InstanceOf = function(ctor) { |
108 | goog.testing.mockmatchers.ArgumentMatcher.call(this, |
109 | function(obj) { |
110 | return obj instanceof ctor; |
111 | // NOTE: Browser differences on ctor.toString() output |
112 | // make using that here problematic. So for now, just let |
113 | // people know the instanceOf() failed without providing |
114 | // browser specific details... |
115 | }, 'instanceOf()'); |
116 | }; |
117 | goog.inherits(goog.testing.mockmatchers.InstanceOf, |
118 | goog.testing.mockmatchers.ArgumentMatcher); |
119 | |
120 | |
121 | |
122 | /** |
123 | * A matcher that verifies that an argument is of a given type (e.g. "object"). |
124 | * @param {string} type The type that a given argument must have. |
125 | * @constructor |
126 | * @extends {goog.testing.mockmatchers.ArgumentMatcher} |
127 | * @final |
128 | */ |
129 | goog.testing.mockmatchers.TypeOf = function(type) { |
130 | goog.testing.mockmatchers.ArgumentMatcher.call(this, |
131 | function(obj) { |
132 | return goog.typeOf(obj) == type; |
133 | }, 'typeOf(' + type + ')'); |
134 | }; |
135 | goog.inherits(goog.testing.mockmatchers.TypeOf, |
136 | goog.testing.mockmatchers.ArgumentMatcher); |
137 | |
138 | |
139 | |
140 | /** |
141 | * A matcher that verifies that an argument matches a given RegExp. |
142 | * @param {RegExp} regexp The regular expression that the argument must match. |
143 | * @constructor |
144 | * @extends {goog.testing.mockmatchers.ArgumentMatcher} |
145 | * @final |
146 | */ |
147 | goog.testing.mockmatchers.RegexpMatch = function(regexp) { |
148 | goog.testing.mockmatchers.ArgumentMatcher.call(this, |
149 | function(str) { |
150 | return regexp.test(str); |
151 | }, 'match(' + regexp + ')'); |
152 | }; |
153 | goog.inherits(goog.testing.mockmatchers.RegexpMatch, |
154 | goog.testing.mockmatchers.ArgumentMatcher); |
155 | |
156 | |
157 | |
158 | /** |
159 | * A matcher that always returns true. It is useful when the user does not care |
160 | * for some arguments. |
161 | * For example: mockFunction('username', 'password', IgnoreArgument); |
162 | * @constructor |
163 | * @extends {goog.testing.mockmatchers.ArgumentMatcher} |
164 | * @final |
165 | */ |
166 | goog.testing.mockmatchers.IgnoreArgument = function() { |
167 | goog.testing.mockmatchers.ArgumentMatcher.call(this, |
168 | function() { |
169 | return true; |
170 | }, 'true'); |
171 | }; |
172 | goog.inherits(goog.testing.mockmatchers.IgnoreArgument, |
173 | goog.testing.mockmatchers.ArgumentMatcher); |
174 | |
175 | |
176 | |
177 | /** |
178 | * A matcher that verifies that the argument is an object that equals the given |
179 | * expected object, using a deep comparison. |
180 | * @param {Object} expectedObject An object to match against when |
181 | * verifying the argument. |
182 | * @constructor |
183 | * @extends {goog.testing.mockmatchers.ArgumentMatcher} |
184 | */ |
185 | goog.testing.mockmatchers.ObjectEquals = function(expectedObject) { |
186 | goog.testing.mockmatchers.ArgumentMatcher.call(this, |
187 | function(matchObject) { |
188 | assertObjectEquals('Expected equal objects', expectedObject, |
189 | matchObject); |
190 | return true; |
191 | }, 'objectEquals(' + expectedObject + ')'); |
192 | }; |
193 | goog.inherits(goog.testing.mockmatchers.ObjectEquals, |
194 | goog.testing.mockmatchers.ArgumentMatcher); |
195 | |
196 | |
197 | /** @override */ |
198 | goog.testing.mockmatchers.ObjectEquals.prototype.matches = |
199 | function(toVerify, opt_expectation) { |
200 | // Override the default matches implementation to capture the exception thrown |
201 | // by assertObjectEquals (if any) and add that message to the expectation. |
202 | try { |
203 | return goog.testing.mockmatchers.ObjectEquals.superClass_.matches.call( |
204 | this, toVerify, opt_expectation); |
205 | } catch (e) { |
206 | if (opt_expectation) { |
207 | opt_expectation.addErrorMessage(e.message); |
208 | } |
209 | return false; |
210 | } |
211 | }; |
212 | |
213 | |
214 | |
215 | /** |
216 | * A matcher that saves the argument that it is verifying so that your unit test |
217 | * can perform extra tests with this argument later. For example, if the |
218 | * argument is a callback method, the unit test can then later call this |
219 | * callback to test the asynchronous portion of the call. |
220 | * @param {goog.testing.mockmatchers.ArgumentMatcher|Function=} opt_matcher |
221 | * Argument matcher or matching function that will be used to validate the |
222 | * argument. By default, argument will always be valid. |
223 | * @param {?string=} opt_matchName The name expressing intent as part of |
224 | * an error message for when a match fails. |
225 | * @constructor |
226 | * @extends {goog.testing.mockmatchers.ArgumentMatcher} |
227 | * @final |
228 | */ |
229 | goog.testing.mockmatchers.SaveArgument = function(opt_matcher, opt_matchName) { |
230 | goog.testing.mockmatchers.ArgumentMatcher.call( |
231 | this, /** @type {Function} */ (opt_matcher), opt_matchName); |
232 | |
233 | if (opt_matcher instanceof goog.testing.mockmatchers.ArgumentMatcher) { |
234 | /** |
235 | * Delegate match requests to this matcher. |
236 | * @type {goog.testing.mockmatchers.ArgumentMatcher} |
237 | * @private |
238 | */ |
239 | this.delegateMatcher_ = opt_matcher; |
240 | } else if (!opt_matcher) { |
241 | this.delegateMatcher_ = goog.testing.mockmatchers.ignoreArgument; |
242 | } |
243 | }; |
244 | goog.inherits(goog.testing.mockmatchers.SaveArgument, |
245 | goog.testing.mockmatchers.ArgumentMatcher); |
246 | |
247 | |
248 | /** @override */ |
249 | goog.testing.mockmatchers.SaveArgument.prototype.matches = function( |
250 | toVerify, opt_expectation) { |
251 | this.arg = toVerify; |
252 | if (this.delegateMatcher_) { |
253 | return this.delegateMatcher_.matches(toVerify, opt_expectation); |
254 | } |
255 | return goog.testing.mockmatchers.SaveArgument.superClass_.matches.call( |
256 | this, toVerify, opt_expectation); |
257 | }; |
258 | |
259 | |
260 | /** |
261 | * Saved argument that was verified. |
262 | * @type {*} |
263 | */ |
264 | goog.testing.mockmatchers.SaveArgument.prototype.arg; |
265 | |
266 | |
267 | /** |
268 | * An instance of the IgnoreArgument matcher. Returns true for all matches. |
269 | * @type {goog.testing.mockmatchers.IgnoreArgument} |
270 | */ |
271 | goog.testing.mockmatchers.ignoreArgument = |
272 | new goog.testing.mockmatchers.IgnoreArgument(); |
273 | |
274 | |
275 | /** |
276 | * A matcher that verifies that an argument is an array. |
277 | * @type {goog.testing.mockmatchers.ArgumentMatcher} |
278 | */ |
279 | goog.testing.mockmatchers.isArray = |
280 | new goog.testing.mockmatchers.ArgumentMatcher(goog.isArray, |
281 | 'isArray'); |
282 | |
283 | |
284 | /** |
285 | * A matcher that verifies that an argument is a array-like. A NodeList is an |
286 | * example of a collection that is very close to an array. |
287 | * @type {goog.testing.mockmatchers.ArgumentMatcher} |
288 | */ |
289 | goog.testing.mockmatchers.isArrayLike = |
290 | new goog.testing.mockmatchers.ArgumentMatcher(goog.isArrayLike, |
291 | 'isArrayLike'); |
292 | |
293 | |
294 | /** |
295 | * A matcher that verifies that an argument is a date-like. |
296 | * @type {goog.testing.mockmatchers.ArgumentMatcher} |
297 | */ |
298 | goog.testing.mockmatchers.isDateLike = |
299 | new goog.testing.mockmatchers.ArgumentMatcher(goog.isDateLike, |
300 | 'isDateLike'); |
301 | |
302 | |
303 | /** |
304 | * A matcher that verifies that an argument is a string. |
305 | * @type {goog.testing.mockmatchers.ArgumentMatcher} |
306 | */ |
307 | goog.testing.mockmatchers.isString = |
308 | new goog.testing.mockmatchers.ArgumentMatcher(goog.isString, |
309 | 'isString'); |
310 | |
311 | |
312 | /** |
313 | * A matcher that verifies that an argument is a boolean. |
314 | * @type {goog.testing.mockmatchers.ArgumentMatcher} |
315 | */ |
316 | goog.testing.mockmatchers.isBoolean = |
317 | new goog.testing.mockmatchers.ArgumentMatcher(goog.isBoolean, |
318 | 'isBoolean'); |
319 | |
320 | |
321 | /** |
322 | * A matcher that verifies that an argument is a number. |
323 | * @type {goog.testing.mockmatchers.ArgumentMatcher} |
324 | */ |
325 | goog.testing.mockmatchers.isNumber = |
326 | new goog.testing.mockmatchers.ArgumentMatcher(goog.isNumber, |
327 | 'isNumber'); |
328 | |
329 | |
330 | /** |
331 | * A matcher that verifies that an argument is a function. |
332 | * @type {goog.testing.mockmatchers.ArgumentMatcher} |
333 | */ |
334 | goog.testing.mockmatchers.isFunction = |
335 | new goog.testing.mockmatchers.ArgumentMatcher(goog.isFunction, |
336 | 'isFunction'); |
337 | |
338 | |
339 | /** |
340 | * A matcher that verifies that an argument is an object. |
341 | * @type {goog.testing.mockmatchers.ArgumentMatcher} |
342 | */ |
343 | goog.testing.mockmatchers.isObject = |
344 | new goog.testing.mockmatchers.ArgumentMatcher(goog.isObject, |
345 | 'isObject'); |
346 | |
347 | |
348 | /** |
349 | * A matcher that verifies that an argument is like a DOM node. |
350 | * @type {goog.testing.mockmatchers.ArgumentMatcher} |
351 | */ |
352 | goog.testing.mockmatchers.isNodeLike = |
353 | new goog.testing.mockmatchers.ArgumentMatcher(goog.dom.isNodeLike, |
354 | 'isNodeLike'); |
355 | |
356 | |
357 | /** |
358 | * A function that checks to see if an array matches a given set of |
359 | * expectations. The expectations array can be a mix of ArgumentMatcher |
360 | * implementations and values. True will be returned if values are identical or |
361 | * if a matcher returns a positive result. |
362 | * @param {Array} expectedArr An array of expectations which can be either |
363 | * values to check for equality or ArgumentMatchers. |
364 | * @param {Array} arr The array to match. |
365 | * @param {goog.testing.MockExpectation?=} opt_expectation The expectation |
366 | * for this match. |
367 | * @return {boolean} Whether or not the given array matches the expectations. |
368 | */ |
369 | goog.testing.mockmatchers.flexibleArrayMatcher = |
370 | function(expectedArr, arr, opt_expectation) { |
371 | return goog.array.equals(expectedArr, arr, function(a, b) { |
372 | var errCount = 0; |
373 | if (opt_expectation) { |
374 | errCount = opt_expectation.getErrorMessageCount(); |
375 | } |
376 | var isamatch = a === b || |
377 | a instanceof goog.testing.mockmatchers.ArgumentMatcher && |
378 | a.matches(b, opt_expectation); |
379 | var failureMessage = null; |
380 | if (!isamatch) { |
381 | failureMessage = goog.testing.asserts.findDifferences(a, b); |
382 | isamatch = !failureMessage; |
383 | } |
384 | if (!isamatch && opt_expectation) { |
385 | // If the error count changed, the match sent out an error |
386 | // message. If the error count has not changed, then |
387 | // we need to send out an error message... |
388 | if (errCount == opt_expectation.getErrorMessageCount()) { |
389 | // Use the _displayStringForValue() from assert.js |
390 | // for consistency... |
391 | if (!failureMessage) { |
392 | failureMessage = 'Expected: ' + _displayStringForValue(a) + |
393 | ' but was: ' + _displayStringForValue(b); |
394 | } |
395 | opt_expectation.addErrorMessage(failureMessage); |
396 | } |
397 | } |
398 | return isamatch; |
399 | }); |
400 | }; |