# lib/goog/testing/loosemock.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 at6//7//      http://www.apache.org/licenses/LICENSE-2.08//9// Unless required by applicable law or agreed to in writing, software10// 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 and13// limitations under the License.1415/**16 * @fileoverview This file defines a loose mock implementation.17 */1819goog.provide('goog.testing.LooseExpectationCollection');20goog.provide('goog.testing.LooseMock');2122goog.require('goog.array');23goog.require('goog.structs.Map');24goog.require('goog.testing.Mock');25262728/**29 * This class is an ordered collection of expectations for one method. Since30 * the loose mock does most of its verification at the time of $verify, this31 * class is necessary to manage the return/throw behavior when the mock is32 * being called.33 * @constructor34 * @final35 */36goog.testing.LooseExpectationCollection = function() {37  /**38   * The list of expectations. All of these should have the same name.39   * @type {Array.<goog.testing.MockExpectation>}40   * @private41   */42  this.expectations_ = [];43};444546/**47 * Adds an expectation to this collection.48 * @param {goog.testing.MockExpectation} expectation The expectation to add.49 */50goog.testing.LooseExpectationCollection.prototype.addExpectation =51    function(expectation) {52  this.expectations_.push(expectation);53};545556/**57 * Gets the list of expectations in this collection.58 * @return {Array.<goog.testing.MockExpectation>} The array of expectations.59 */60goog.testing.LooseExpectationCollection.prototype.getExpectations = function() {61  return this.expectations_;62};63646566/**67 * This is a mock that does not care about the order of method calls. As a68 * result, it won't throw exceptions until verify() is called. The only69 * exception is that if a method is called that has no expectations, then an70 * exception will be thrown.71 * @param {Object|Function} objectToMock The object that should be mocked, or72 *    the constructor of an object to mock.73 * @param {boolean=} opt_ignoreUnexpectedCalls Whether to ignore unexpected74 *     calls.75 * @param {boolean=} opt_mockStaticMethods An optional argument denoting that76 *     a mock should be constructed from the static functions of a class.77 * @param {boolean=} opt_createProxy An optional argument denoting that78 *     a proxy for the target mock should be created.79 * @constructor80 * @extends {goog.testing.Mock}81 */82goog.testing.LooseMock = function(objectToMock, opt_ignoreUnexpectedCalls,83    opt_mockStaticMethods, opt_createProxy) {84  goog.testing.Mock.call(this, objectToMock, opt_mockStaticMethods,85      opt_createProxy);8687  /**88   * A map of method names to a LooseExpectationCollection for that method.89   * @type {goog.structs.Map}90   * @private91   */92  this.$expectations_ = new goog.structs.Map();9394  /**95   * The calls that have been made; we cache them to verify at the end. Each96   * element is an array where the first element is the name, and the second97   * element is the arguments.98   * @type {Array.<Array.<*>>}99   * @private100   */101  this.$calls_ = [];102103  /**104   * Whether to ignore unexpected calls.105   * @type {boolean}106   * @private107   */108  this.$ignoreUnexpectedCalls_ = !!opt_ignoreUnexpectedCalls;109};110goog.inherits(goog.testing.LooseMock, goog.testing.Mock);111112113/**114 * A setter for the ignoreUnexpectedCalls field.115 * @param {boolean} ignoreUnexpectedCalls Whether to ignore unexpected calls.116 * @return {!goog.testing.LooseMock} This mock object.117 */118goog.testing.LooseMock.prototype.$setIgnoreUnexpectedCalls = function(119    ignoreUnexpectedCalls) {120  this.$ignoreUnexpectedCalls_ = ignoreUnexpectedCalls;121  return this;122};123124125/** @override */126goog.testing.LooseMock.prototype.$recordExpectation = function() {127  if (!this.$expectations_.containsKey(this.$pendingExpectation.name)) {128    this.$expectations_.set(this.$pendingExpectation.name,129        new goog.testing.LooseExpectationCollection());130  }131132  var collection = this.$expectations_.get(this.$pendingExpectation.name);133  collection.addExpectation(this.$pendingExpectation);134};135136137/** @override */138goog.testing.LooseMock.prototype.$recordCall = function(name, args) {139  if (!this.$expectations_.containsKey(name)) {140    if (this.$ignoreUnexpectedCalls_) {141      return;142    }143    this.$throwCallException(name, args);144  }145146  // Start from the beginning of the expectations for this name,147  // and iterate over them until we find an expectation that matches148  // and also has calls remaining.149  var collection = this.$expectations_.get(name);150  var matchingExpectation = null;151  var expectations = collection.getExpectations();152  for (var i = 0; i < expectations.length; i++) {153    var expectation = expectations[i];154    if (this.$verifyCall(expectation, name, args)) {155      matchingExpectation = expectation;156      if (expectation.actualCalls < expectation.maxCalls) {157        break;158      } // else continue and see if we can find something that does match159    }160  }161  if (matchingExpectation == null) {162    this.$throwCallException(name, args, expectation);163  }164165  matchingExpectation.actualCalls++;166  if (matchingExpectation.actualCalls > matchingExpectation.maxCalls) {167    this.$throwException('Too many calls to ' + matchingExpectation.name +168            '\nExpected: ' + matchingExpectation.maxCalls + ' but was: ' +169            matchingExpectation.actualCalls);170  }171172  this.$calls_.push([name, args]);173  return this.$do(matchingExpectation, args);174};175176177/** @override */178goog.testing.LooseMock.prototype.$reset = function() {179  goog.testing.LooseMock.superClass_.$reset.call(this);180181  this.$expectations_ = new goog.structs.Map();182  this.$calls_ = [];183};184185186/** @override */187goog.testing.LooseMock.prototype.$replay = function() {188  goog.testing.LooseMock.superClass_.$replay.call(this);189190  // Verify that there are no expectations that can never be reached.191  // This can't catch every situation, but it is a decent sanity check192  // and it's similar to the behavior of EasyMock in java.193  var collections = this.$expectations_.getValues();194  for (var i = 0; i < collections.length; i++) {195    var expectations = collections[i].getExpectations();196    for (var j = 0; j < expectations.length; j++) {197      var expectation = expectations[j];198      // If this expectation can be called infinite times, then199      // check if any subsequent expectation has the exact same200      // argument list.201      if (!isFinite(expectation.maxCalls)) {202        for (var k = j + 1; k < expectations.length; k++) {203          var laterExpectation = expectations[k];204          if (laterExpectation.minCalls > 0 &&205              goog.array.equals(expectation.argumentList,206                  laterExpectation.argumentList)) {207            var name = expectation.name;208            var argsString = this.$argumentsAsString(expectation.argumentList);209            this.$throwException([210              'Expected call to ', name, ' with arguments ', argsString,211              ' has an infinite max number of calls; can\'t expect an',212              ' identical call later with a positive min number of calls'213            ].join(''));214          }215        }216      }217    }218  }219};220221222/** @override */223goog.testing.LooseMock.prototype.$verify = function() {224  goog.testing.LooseMock.superClass_.$verify.call(this);225  var collections = this.$expectations_.getValues();226227  for (var i = 0; i < collections.length; i++) {228    var expectations = collections[i].getExpectations();229    for (var j = 0; j < expectations.length; j++) {230      var expectation = expectations[j];231      if (expectation.actualCalls > expectation.maxCalls) {232        this.$throwException('Too many calls to ' + expectation.name +233            '\nExpected: ' + expectation.maxCalls + ' but was: ' +234            expectation.actualCalls);235      } else if (expectation.actualCalls < expectation.minCalls) {236        this.$throwException('Not enough calls to ' + expectation.name +237            '\nExpected: ' + expectation.minCalls + ' but was: ' +238            expectation.actualCalls);239      }240    }241  }242};