| 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 This file defines a strict mock implementation. |
| 17 | */ |
| 18 | |
| 19 | goog.provide('goog.testing.StrictMock'); |
| 20 | |
| 21 | goog.require('goog.array'); |
| 22 | goog.require('goog.testing.Mock'); |
| 23 | |
| 24 | |
| 25 | |
| 26 | /** |
| 27 | * This is a mock that verifies that methods are called in the order that they |
| 28 | * are specified during the recording phase. Since it verifies order, it |
| 29 | * follows 'fail fast' semantics. If it detects a deviation from the |
| 30 | * expectations, it will throw an exception and not wait for verify to be |
| 31 | * called. |
| 32 | * @param {Object|Function} objectToMock The object that should be mocked, or |
| 33 | * the constructor of an object to mock. |
| 34 | * @param {boolean=} opt_mockStaticMethods An optional argument denoting that |
| 35 | * a mock should be constructed from the static functions of a class. |
| 36 | * @param {boolean=} opt_createProxy An optional argument denoting that |
| 37 | * a proxy for the target mock should be created. |
| 38 | * @constructor |
| 39 | * @extends {goog.testing.Mock} |
| 40 | * @final |
| 41 | */ |
| 42 | goog.testing.StrictMock = function(objectToMock, opt_mockStaticMethods, |
| 43 | opt_createProxy) { |
| 44 | goog.testing.Mock.call(this, objectToMock, opt_mockStaticMethods, |
| 45 | opt_createProxy); |
| 46 | |
| 47 | /** |
| 48 | * An array of MockExpectations. |
| 49 | * @type {Array<goog.testing.MockExpectation>} |
| 50 | * @private |
| 51 | */ |
| 52 | this.$expectations_ = []; |
| 53 | }; |
| 54 | goog.inherits(goog.testing.StrictMock, goog.testing.Mock); |
| 55 | |
| 56 | |
| 57 | /** @override */ |
| 58 | goog.testing.StrictMock.prototype.$recordExpectation = function() { |
| 59 | this.$expectations_.push(this.$pendingExpectation); |
| 60 | }; |
| 61 | |
| 62 | |
| 63 | /** @override */ |
| 64 | goog.testing.StrictMock.prototype.$recordCall = function(name, args) { |
| 65 | if (this.$expectations_.length == 0) { |
| 66 | this.$throwCallException(name, args); |
| 67 | } |
| 68 | |
| 69 | // If the current expectation has a different name, make sure it was called |
| 70 | // enough and then discard it. We're through with it. |
| 71 | var currentExpectation = this.$expectations_[0]; |
| 72 | while (!this.$verifyCall(currentExpectation, name, args)) { |
| 73 | |
| 74 | // This might be an item which has passed its min, and we can now |
| 75 | // look past it, or it might be below its min and generate an error. |
| 76 | if (currentExpectation.actualCalls < currentExpectation.minCalls) { |
| 77 | this.$throwCallException(name, args, currentExpectation); |
| 78 | } |
| 79 | |
| 80 | this.$expectations_.shift(); |
| 81 | if (this.$expectations_.length < 1) { |
| 82 | // Nothing left, but this may be a failed attempt to call the previous |
| 83 | // item on the list, which may have been between its min and max. |
| 84 | this.$throwCallException(name, args, currentExpectation); |
| 85 | } |
| 86 | currentExpectation = this.$expectations_[0]; |
| 87 | } |
| 88 | |
| 89 | if (currentExpectation.maxCalls == 0) { |
| 90 | this.$throwCallException(name, args); |
| 91 | } |
| 92 | |
| 93 | currentExpectation.actualCalls++; |
| 94 | // If we hit the max number of calls for this expectation, we're finished |
| 95 | // with it. |
| 96 | if (currentExpectation.actualCalls == currentExpectation.maxCalls) { |
| 97 | this.$expectations_.shift(); |
| 98 | } |
| 99 | |
| 100 | return this.$do(currentExpectation, args); |
| 101 | }; |
| 102 | |
| 103 | |
| 104 | /** @override */ |
| 105 | goog.testing.StrictMock.prototype.$reset = function() { |
| 106 | goog.testing.StrictMock.superClass_.$reset.call(this); |
| 107 | |
| 108 | goog.array.clear(this.$expectations_); |
| 109 | }; |
| 110 | |
| 111 | |
| 112 | /** @override */ |
| 113 | goog.testing.StrictMock.prototype.$verify = function() { |
| 114 | goog.testing.StrictMock.superClass_.$verify.call(this); |
| 115 | |
| 116 | while (this.$expectations_.length > 0) { |
| 117 | var expectation = this.$expectations_[0]; |
| 118 | if (expectation.actualCalls < expectation.minCalls) { |
| 119 | this.$throwException('Missing a call to ' + expectation.name + |
| 120 | '\nExpected: ' + expectation.minCalls + ' but was: ' + |
| 121 | expectation.actualCalls); |
| 122 | |
| 123 | } else { |
| 124 | // Don't need to check max, that's handled when the call is made |
| 125 | this.$expectations_.shift(); |
| 126 | } |
| 127 | } |
| 128 | }; |
| 129 | |
| 130 | |