Path: blob/trunk/third_party/closure/goog/testing/loosemock.js
4058 views
/**1* @license2* Copyright The Closure Library Authors.3* SPDX-License-Identifier: Apache-2.04*/56/**7* @fileoverview This file defines a loose mock implementation.8*/910goog.setTestOnly('goog.testing.LooseExpectationCollection');11goog.provide('goog.testing.LooseExpectationCollection');12goog.provide('goog.testing.LooseMock');1314goog.require('goog.array');15goog.require('goog.asserts');16goog.require('goog.testing.Mock');17goog.requireType('goog.testing.MockExpectation');18192021/**22* This class is an ordered collection of expectations for one method. Since23* the loose mock does most of its verification at the time of $verify, this24* class is necessary to manage the return/throw behavior when the mock is25* being called.26* @constructor27* @final28*/29goog.testing.LooseExpectationCollection = function() {30'use strict';31/**32* The list of expectations. All of these should have the same name.33* @type {!Array<!goog.testing.MockExpectation>}34* @private35*/36this.expectations_ = [];37};383940/**41* Adds an expectation to this collection.42* @param {!goog.testing.MockExpectation} expectation The expectation to add.43*/44goog.testing.LooseExpectationCollection.prototype.addExpectation = function(45expectation) {46'use strict';47this.expectations_.push(expectation);48};495051/**52* Gets the list of expectations in this collection.53* @return {!Array<!goog.testing.MockExpectation>} The array of expectations.54*/55goog.testing.LooseExpectationCollection.prototype.getExpectations = function() {56'use strict';57return this.expectations_;58};59606162/**63* This is a mock that does not care about the order of method calls. As a64* result, it won't throw exceptions until verify() is called. The only65* exception is that if a method is called that has no expectations, then an66* exception will be thrown.67* @param {Object|Function} objectToMock The object that should be mocked, or68* the constructor of an object to mock.69* @param {boolean=} opt_ignoreUnexpectedCalls Whether to ignore unexpected70* calls.71* @param {boolean=} opt_mockStaticMethods An optional argument denoting that72* a mock should be constructed from the static functions of a class.73* @param {boolean=} opt_createProxy An optional argument denoting that74* a proxy for the target mock should be created.75* @constructor76* @extends {goog.testing.Mock}77*/78goog.testing.LooseMock = function(79objectToMock, opt_ignoreUnexpectedCalls, opt_mockStaticMethods,80opt_createProxy) {81'use strict';82goog.testing.Mock.call(83this, objectToMock, opt_mockStaticMethods, opt_createProxy);8485/**86* A map of method names to a LooseExpectationCollection for that method.87* @type {!Map<string, !goog.testing.LooseExpectationCollection>}88* @private89*/90this.$expectations_ = new Map();9192/** @private {!Set<!goog.testing.MockExpectation>} */93this.awaitingExpectations_ = new Set();9495/**96* The calls that have been made; we cache them to verify at the end. Each97* element is an array where the first element is the name, and the second98* element is the arguments.99* @type {Array<Array<*>>}100* @private101*/102this.$calls_ = [];103104/**105* Whether to ignore unexpected calls.106* @type {boolean}107* @private108*/109this.$ignoreUnexpectedCalls_ = !!opt_ignoreUnexpectedCalls;110};111goog.inherits(goog.testing.LooseMock, goog.testing.Mock);112113114/**115* A setter for the ignoreUnexpectedCalls field.116* @param {boolean} ignoreUnexpectedCalls Whether to ignore unexpected calls.117* @return {!goog.testing.LooseMock} This mock object.118*/119goog.testing.LooseMock.prototype.$setIgnoreUnexpectedCalls = function(120ignoreUnexpectedCalls) {121'use strict';122this.$ignoreUnexpectedCalls_ = ignoreUnexpectedCalls;123return this;124};125126127/** @override */128goog.testing.LooseMock.prototype.$recordExpectation = function() {129'use strict';130if (!this.$expectations_.has(this.$pendingExpectation.name)) {131this.$expectations_.set(132this.$pendingExpectation.name,133new goog.testing.LooseExpectationCollection());134}135136var collection = this.$expectations_.get(this.$pendingExpectation.name);137collection.addExpectation(this.$pendingExpectation);138if (this.$pendingExpectation) {139this.awaitingExpectations_.add(this.$pendingExpectation);140}141};142143144/** @override */145goog.testing.LooseMock.prototype.$recordCall = function(name, args) {146'use strict';147if (!this.$expectations_.has(name)) {148if (this.$ignoreUnexpectedCalls_) {149return;150}151this.$throwCallException(name, args);152}153154// Start from the beginning of the expectations for this name,155// and iterate over them until we find an expectation that matches156// and also has calls remaining.157var collection = this.$expectations_.get(name);158var matchingExpectation = null;159var expectations = collection.getExpectations();160for (var i = 0; i < expectations.length; i++) {161var expectation = expectations[i];162if (this.$verifyCall(expectation, name, args)) {163matchingExpectation = expectation;164if (expectation.actualCalls < expectation.maxCalls) {165break;166} // else continue and see if we can find something that does match167}168}169if (matchingExpectation == null) {170this.$throwCallException(name, args, expectation);171}172173matchingExpectation.actualCalls++;174if (matchingExpectation.actualCalls > matchingExpectation.maxCalls) {175this.$throwException(176'Too many calls to ' + matchingExpectation.name + '\nExpected: ' +177matchingExpectation.maxCalls + ' but was: ' +178matchingExpectation.actualCalls);179}180if (matchingExpectation.actualCalls >= matchingExpectation.minCalls) {181this.awaitingExpectations_.delete(matchingExpectation);182this.maybeFinishedWithExpectations_();183}184185this.$calls_.push([name, args]);186return this.$do(matchingExpectation, args);187};188189190/** @override */191goog.testing.LooseMock.prototype.$reset = function() {192'use strict';193goog.testing.LooseMock.superClass_.$reset.call(this);194195this.$expectations_ = new Map();196this.awaitingExpectations_ = new Set();197this.$calls_ = [];198};199200201/** @override */202goog.testing.LooseMock.prototype.$replay = function() {203'use strict';204goog.testing.LooseMock.superClass_.$replay.call(this);205206// Verify that there are no expectations that can never be reached.207// This can't catch every situation, but it is a decent sanity check208// and it's similar to the behavior of EasyMock in java.209for (const expectationCollection of this.$expectations_.values()) {210var expectations = expectationCollection.getExpectations();211for (var j = 0; j < expectations.length; j++) {212var expectation = expectations[j];213// If this expectation can be called infinite times, then214// check if any subsequent expectation has the exact same215// argument list.216if (!isFinite(expectation.maxCalls)) {217for (var k = j + 1; k < expectations.length; k++) {218var laterExpectation = expectations[k];219if (laterExpectation.minCalls > 0 &&220goog.array.equals(221expectation.argumentList, laterExpectation.argumentList)) {222var name = expectation.name;223var argsString = this.$argumentsAsString(expectation.argumentList);224this.$throwException([225'Expected call to ', name, ' with arguments ', argsString,226' has an infinite max number of calls; can\'t expect an',227' identical call later with a positive min number of calls'228].join(''));229}230}231}232}233}234};235236237/** @override */238goog.testing.LooseMock.prototype.$waitAndVerify = function() {239'use strict';240for (const expectationCollection of this.$expectations_.values()) {241var expectations = expectationCollection.getExpectations();242for (var j = 0; j < expectations.length; j++) {243var expectation = expectations[j];244goog.asserts.assert(245!isFinite(expectation.maxCalls) ||246expectation.minCalls == expectation.maxCalls,247'Mock expectations cannot have a loose number of expected calls to ' +248'use $waitAndVerify.');249}250}251var promise = goog.testing.LooseMock.base(this, '$waitAndVerify');252this.maybeFinishedWithExpectations_();253return promise;254};255256/**257* @private258*/259goog.testing.LooseMock.prototype.maybeFinishedWithExpectations_ = function() {260'use strict';261var unresolvedExpectations = goog.array.some(262Array.from(this.$expectations_.values()),263function(expectationCollection) {264'use strict';265return goog.array.some(266expectationCollection.getExpectations(), function(expectation) {267'use strict';268return expectation.actualCalls < expectation.minCalls;269});270});271if (this.waitingForExpectations && !unresolvedExpectations) {272this.waitingForExpectations.resolve();273}274};275276/** @override */277goog.testing.LooseMock.prototype.$verify = function() {278'use strict';279goog.testing.LooseMock.superClass_.$verify.call(this);280for (const expectationCollection of this.$expectations_.values()) {281var expectations = expectationCollection.getExpectations();282for (var j = 0; j < expectations.length; j++) {283var expectation = expectations[j];284if (expectation.actualCalls > expectation.maxCalls) {285this.$throwException(286'Too many calls to ' + expectation.name + '\nExpected: ' +287expectation.maxCalls + ' but was: ' + expectation.actualCalls);288} else if (expectation.actualCalls < expectation.minCalls) {289this.$throwException(290'Not enough calls to ' + expectation.name + '\nExpected: ' +291expectation.minCalls + ' but was: ' + expectation.actualCalls);292}293}294}295};296297298