// Copyright 2008 The Closure Library Authors. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS-IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. /** * @fileoverview Matchers to be used with the mock utilities. They allow for * flexible matching by type. Custom matchers can be created by passing a * matcher function into an ArgumentMatcher instance. * * For examples, please see the unit test. * */ goog.setTestOnly('goog.testing.mockmatchers'); goog.provide('goog.testing.mockmatchers'); goog.provide('goog.testing.mockmatchers.ArgumentMatcher'); goog.provide('goog.testing.mockmatchers.IgnoreArgument'); goog.provide('goog.testing.mockmatchers.InstanceOf'); goog.provide('goog.testing.mockmatchers.ObjectEquals'); goog.provide('goog.testing.mockmatchers.RegexpMatch'); goog.provide('goog.testing.mockmatchers.SaveArgument'); goog.provide('goog.testing.mockmatchers.TypeOf'); goog.require('goog.array'); goog.require('goog.dom'); goog.require('goog.testing.asserts'); goog.forwardDeclare('goog.testing.MockExpectation'); // circular /** * A simple interface for executing argument matching. A match in this case is * testing to see if a supplied object fits a given criteria. True is returned * if the given criteria is met. * @param {Function=} opt_matchFn A function that evaluates a given argument * and returns true if it meets a given criteria. * @param {?string=} opt_matchName The name expressing intent as part of * an error message for when a match fails. * @constructor */ goog.testing.mockmatchers.ArgumentMatcher = function( opt_matchFn, opt_matchName) { /** * A function that evaluates a given argument and returns true if it meets a * given criteria. * @type {Function} * @private */ this.matchFn_ = opt_matchFn || null; /** * A string indicating the match intent (e.g. isBoolean or isString). * @type {?string} * @private */ this.matchName_ = opt_matchName || null; }; /** * A function that takes a match argument and an optional MockExpectation * which (if provided) will get error information and returns whether or * not it matches. * @param {*} toVerify The argument that should be verified. * @param {goog.testing.MockExpectation?=} opt_expectation The expectation * for this match. * @return {boolean} Whether or not a given argument passes verification. */ goog.testing.mockmatchers.ArgumentMatcher.prototype.matches = function( toVerify, opt_expectation) { if (this.matchFn_) { var isamatch = this.matchFn_(toVerify); if (!isamatch && opt_expectation) { if (this.matchName_) { opt_expectation.addErrorMessage( 'Expected: ' + this.matchName_ + ' but was: ' + _displayStringForValue(toVerify)); } else { opt_expectation.addErrorMessage( 'Expected: missing mockmatcher' + ' description but was: ' + _displayStringForValue(toVerify)); } } return isamatch; } else { throw Error('No match function defined for this mock matcher'); } }; /** * A matcher that verifies that an argument is an instance of a given class. * @param {Function} ctor The class that will be used for verification. * @constructor * @extends {goog.testing.mockmatchers.ArgumentMatcher} * @final */ goog.testing.mockmatchers.InstanceOf = function(ctor) { goog.testing.mockmatchers.ArgumentMatcher.call(this, function(obj) { return obj instanceof ctor; // NOTE: Browser differences on ctor.toString() output // make using that here problematic. So for now, just let // people know the instanceOf() failed without providing // browser specific details... }, 'instanceOf()'); }; goog.inherits( goog.testing.mockmatchers.InstanceOf, goog.testing.mockmatchers.ArgumentMatcher); /** * A matcher that verifies that an argument is of a given type (e.g. "object"). * @param {string} type The type that a given argument must have. * @constructor * @extends {goog.testing.mockmatchers.ArgumentMatcher} * @final */ goog.testing.mockmatchers.TypeOf = function(type) { goog.testing.mockmatchers.ArgumentMatcher.call(this, function(obj) { return goog.typeOf(obj) == type; }, 'typeOf(' + type + ')'); }; goog.inherits( goog.testing.mockmatchers.TypeOf, goog.testing.mockmatchers.ArgumentMatcher); /** * A matcher that verifies that an argument matches a given RegExp. * @param {RegExp} regexp The regular expression that the argument must match. * @constructor * @extends {goog.testing.mockmatchers.ArgumentMatcher} * @final */ goog.testing.mockmatchers.RegexpMatch = function(regexp) { goog.testing.mockmatchers.ArgumentMatcher.call(this, function(str) { return regexp.test(str); }, 'match(' + regexp + ')'); }; goog.inherits( goog.testing.mockmatchers.RegexpMatch, goog.testing.mockmatchers.ArgumentMatcher); /** * A matcher that always returns true. It is useful when the user does not care * for some arguments. * For example: mockFunction('username', 'password', IgnoreArgument); * @constructor * @extends {goog.testing.mockmatchers.ArgumentMatcher} * @final */ goog.testing.mockmatchers.IgnoreArgument = function() { goog.testing.mockmatchers.ArgumentMatcher.call( this, function() { return true; }, 'true'); }; goog.inherits( goog.testing.mockmatchers.IgnoreArgument, goog.testing.mockmatchers.ArgumentMatcher); /** * A matcher that verifies that the argument is an object that equals the given * expected object, using a deep comparison. * @param {Object} expectedObject An object to match against when * verifying the argument. * @constructor * @extends {goog.testing.mockmatchers.ArgumentMatcher} */ goog.testing.mockmatchers.ObjectEquals = function(expectedObject) { /** @private */ this.expectedObject_ = expectedObject; }; goog.inherits( goog.testing.mockmatchers.ObjectEquals, goog.testing.mockmatchers.ArgumentMatcher); /** @override */ goog.testing.mockmatchers.ObjectEquals.prototype.matches = function( toVerify, opt_expectation) { // Override the default matches implementation to provide a custom error // message to opt_expectation if it exists. var differences = goog.testing.asserts.findDifferences(this.expectedObject_, toVerify); if (differences) { if (opt_expectation) { opt_expectation.addErrorMessage('Expected equal objects\n' + differences); } return false; } return true; }; /** * A matcher that saves the argument that it is verifying so that your unit test * can perform extra tests with this argument later. For example, if the * argument is a callback method, the unit test can then later call this * callback to test the asynchronous portion of the call. * @param {goog.testing.mockmatchers.ArgumentMatcher|Function=} opt_matcher * Argument matcher or matching function that will be used to validate the * argument. By default, argument will always be valid. * @param {?string=} opt_matchName The name expressing intent as part of * an error message for when a match fails. * @constructor * @extends {goog.testing.mockmatchers.ArgumentMatcher} * @final */ goog.testing.mockmatchers.SaveArgument = function(opt_matcher, opt_matchName) { goog.testing.mockmatchers.ArgumentMatcher.call( this, /** @type {Function} */ (opt_matcher), opt_matchName); if (opt_matcher instanceof goog.testing.mockmatchers.ArgumentMatcher) { /** * Delegate match requests to this matcher. * @type {goog.testing.mockmatchers.ArgumentMatcher} * @private */ this.delegateMatcher_ = opt_matcher; } else if (!opt_matcher) { this.delegateMatcher_ = goog.testing.mockmatchers.ignoreArgument; } }; goog.inherits( goog.testing.mockmatchers.SaveArgument, goog.testing.mockmatchers.ArgumentMatcher); /** @override */ goog.testing.mockmatchers.SaveArgument.prototype.matches = function( toVerify, opt_expectation) { this.arg = toVerify; if (this.delegateMatcher_) { return this.delegateMatcher_.matches(toVerify, opt_expectation); } return goog.testing.mockmatchers.SaveArgument.superClass_.matches.call( this, toVerify, opt_expectation); }; /** * Saved argument that was verified. * @type {*} */ goog.testing.mockmatchers.SaveArgument.prototype.arg; /** * An instance of the IgnoreArgument matcher. Returns true for all matches. * @type {goog.testing.mockmatchers.IgnoreArgument} */ goog.testing.mockmatchers.ignoreArgument = new goog.testing.mockmatchers.IgnoreArgument(); /** * A matcher that verifies that an argument is an array. * @type {goog.testing.mockmatchers.ArgumentMatcher} */ goog.testing.mockmatchers.isArray = new goog.testing.mockmatchers.ArgumentMatcher(goog.isArray, 'isArray'); /** * A matcher that verifies that an argument is a array-like. A NodeList is an * example of a collection that is very close to an array. * @type {goog.testing.mockmatchers.ArgumentMatcher} */ goog.testing.mockmatchers.isArrayLike = new goog.testing.mockmatchers.ArgumentMatcher( goog.isArrayLike, 'isArrayLike'); /** * A matcher that verifies that an argument is a date-like. * @type {goog.testing.mockmatchers.ArgumentMatcher} */ goog.testing.mockmatchers.isDateLike = new goog.testing.mockmatchers.ArgumentMatcher( goog.isDateLike, 'isDateLike'); /** * A matcher that verifies that an argument is a string. * @type {goog.testing.mockmatchers.ArgumentMatcher} */ goog.testing.mockmatchers.isString = new goog.testing.mockmatchers.ArgumentMatcher(goog.isString, 'isString'); /** * A matcher that verifies that an argument is a boolean. * @type {goog.testing.mockmatchers.ArgumentMatcher} */ goog.testing.mockmatchers.isBoolean = new goog.testing.mockmatchers.ArgumentMatcher(goog.isBoolean, 'isBoolean'); /** * A matcher that verifies that an argument is a number. * @type {goog.testing.mockmatchers.ArgumentMatcher} */ goog.testing.mockmatchers.isNumber = new goog.testing.mockmatchers.ArgumentMatcher(goog.isNumber, 'isNumber'); /** * A matcher that verifies that an argument is a function. * @type {goog.testing.mockmatchers.ArgumentMatcher} */ goog.testing.mockmatchers.isFunction = new goog.testing.mockmatchers.ArgumentMatcher( goog.isFunction, 'isFunction'); /** * A matcher that verifies that an argument is an object. * @type {goog.testing.mockmatchers.ArgumentMatcher} */ goog.testing.mockmatchers.isObject = new goog.testing.mockmatchers.ArgumentMatcher(goog.isObject, 'isObject'); /** * A matcher that verifies that an argument is like a DOM node. * @type {goog.testing.mockmatchers.ArgumentMatcher} */ goog.testing.mockmatchers.isNodeLike = new goog.testing.mockmatchers.ArgumentMatcher( goog.dom.isNodeLike, 'isNodeLike'); /** * A function that checks to see if an array matches a given set of * expectations. The expectations array can be a mix of ArgumentMatcher * implementations and values. True will be returned if values are identical or * if a matcher returns a positive result. * @param {Array} expectedArr An array of expectations which can be either * values to check for equality or ArgumentMatchers. * @param {Array} arr The array to match. * @param {goog.testing.MockExpectation?=} opt_expectation The expectation * for this match. * @return {boolean} Whether or not the given array matches the expectations. */ goog.testing.mockmatchers.flexibleArrayMatcher = function( expectedArr, arr, opt_expectation) { return goog.array.equals(expectedArr, arr, function(a, b) { var errCount = 0; if (opt_expectation) { errCount = opt_expectation.getErrorMessageCount(); } var isamatch = a === b || a instanceof goog.testing.mockmatchers.ArgumentMatcher && a.matches(b, opt_expectation); var failureMessage = null; if (!isamatch) { failureMessage = goog.testing.asserts.findDifferences(a, b); isamatch = !failureMessage; } if (!isamatch && opt_expectation) { // If the error count changed, the match sent out an error // message. If the error count has not changed, then // we need to send out an error message... if (errCount == opt_expectation.getErrorMessageCount()) { // Use the _displayStringForValue() from assert.js // for consistency... if (!failureMessage) { failureMessage = 'Expected: ' + _displayStringForValue(a) + ' but was: ' + _displayStringForValue(b); } opt_expectation.addErrorMessage(failureMessage); } } return isamatch; }); };