lib/goog/testing/mockmatchers.js

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
25goog.provide('goog.testing.mockmatchers');
26goog.provide('goog.testing.mockmatchers.ArgumentMatcher');
27goog.provide('goog.testing.mockmatchers.IgnoreArgument');
28goog.provide('goog.testing.mockmatchers.InstanceOf');
29goog.provide('goog.testing.mockmatchers.ObjectEquals');
30goog.provide('goog.testing.mockmatchers.RegexpMatch');
31goog.provide('goog.testing.mockmatchers.SaveArgument');
32goog.provide('goog.testing.mockmatchers.TypeOf');
33
34goog.require('goog.array');
35goog.require('goog.dom');
36goog.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 */
50goog.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 */
78goog.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 */
107goog.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};
117goog.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 */
129goog.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};
135goog.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 */
147goog.testing.mockmatchers.RegexpMatch = function(regexp) {
148 goog.testing.mockmatchers.ArgumentMatcher.call(this,
149 function(str) {
150 return regexp.test(str);
151 }, 'match(' + regexp + ')');
152};
153goog.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 */
166goog.testing.mockmatchers.IgnoreArgument = function() {
167 goog.testing.mockmatchers.ArgumentMatcher.call(this,
168 function() {
169 return true;
170 }, 'true');
171};
172goog.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 */
185goog.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};
193goog.inherits(goog.testing.mockmatchers.ObjectEquals,
194 goog.testing.mockmatchers.ArgumentMatcher);
195
196
197/** @override */
198goog.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 */
229goog.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};
244goog.inherits(goog.testing.mockmatchers.SaveArgument,
245 goog.testing.mockmatchers.ArgumentMatcher);
246
247
248/** @override */
249goog.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 */
264goog.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 */
271goog.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 */
279goog.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 */
289goog.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 */
298goog.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 */
307goog.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 */
316goog.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 */
325goog.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 */
334goog.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 */
343goog.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 */
352goog.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 */
369goog.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};