loosemock.js 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246
  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. * @fileoverview This file defines a loose mock implementation.
  16. */
  17. goog.setTestOnly('goog.testing.LooseExpectationCollection');
  18. goog.provide('goog.testing.LooseExpectationCollection');
  19. goog.provide('goog.testing.LooseMock');
  20. goog.require('goog.array');
  21. goog.require('goog.structs.Map');
  22. goog.require('goog.testing.Mock');
  23. /**
  24. * This class is an ordered collection of expectations for one method. Since
  25. * the loose mock does most of its verification at the time of $verify, this
  26. * class is necessary to manage the return/throw behavior when the mock is
  27. * being called.
  28. * @constructor
  29. * @final
  30. */
  31. goog.testing.LooseExpectationCollection = function() {
  32. /**
  33. * The list of expectations. All of these should have the same name.
  34. * @type {Array<goog.testing.MockExpectation>}
  35. * @private
  36. */
  37. this.expectations_ = [];
  38. };
  39. /**
  40. * Adds an expectation to this collection.
  41. * @param {goog.testing.MockExpectation} expectation The expectation to add.
  42. */
  43. goog.testing.LooseExpectationCollection.prototype.addExpectation = function(
  44. expectation) {
  45. this.expectations_.push(expectation);
  46. };
  47. /**
  48. * Gets the list of expectations in this collection.
  49. * @return {Array<goog.testing.MockExpectation>} The array of expectations.
  50. */
  51. goog.testing.LooseExpectationCollection.prototype.getExpectations = function() {
  52. return this.expectations_;
  53. };
  54. /**
  55. * This is a mock that does not care about the order of method calls. As a
  56. * result, it won't throw exceptions until verify() is called. The only
  57. * exception is that if a method is called that has no expectations, then an
  58. * exception will be thrown.
  59. * @param {Object|Function} objectToMock The object that should be mocked, or
  60. * the constructor of an object to mock.
  61. * @param {boolean=} opt_ignoreUnexpectedCalls Whether to ignore unexpected
  62. * calls.
  63. * @param {boolean=} opt_mockStaticMethods An optional argument denoting that
  64. * a mock should be constructed from the static functions of a class.
  65. * @param {boolean=} opt_createProxy An optional argument denoting that
  66. * a proxy for the target mock should be created.
  67. * @constructor
  68. * @extends {goog.testing.Mock}
  69. */
  70. goog.testing.LooseMock = function(
  71. objectToMock, opt_ignoreUnexpectedCalls, opt_mockStaticMethods,
  72. opt_createProxy) {
  73. goog.testing.Mock.call(
  74. this, objectToMock, opt_mockStaticMethods, opt_createProxy);
  75. /**
  76. * A map of method names to a LooseExpectationCollection for that method.
  77. * @type {goog.structs.Map}
  78. * @private
  79. */
  80. this.$expectations_ = new goog.structs.Map();
  81. /**
  82. * The calls that have been made; we cache them to verify at the end. Each
  83. * element is an array where the first element is the name, and the second
  84. * element is the arguments.
  85. * @type {Array<Array<*>>}
  86. * @private
  87. */
  88. this.$calls_ = [];
  89. /**
  90. * Whether to ignore unexpected calls.
  91. * @type {boolean}
  92. * @private
  93. */
  94. this.$ignoreUnexpectedCalls_ = !!opt_ignoreUnexpectedCalls;
  95. };
  96. goog.inherits(goog.testing.LooseMock, goog.testing.Mock);
  97. /**
  98. * A setter for the ignoreUnexpectedCalls field.
  99. * @param {boolean} ignoreUnexpectedCalls Whether to ignore unexpected calls.
  100. * @return {!goog.testing.LooseMock} This mock object.
  101. */
  102. goog.testing.LooseMock.prototype.$setIgnoreUnexpectedCalls = function(
  103. ignoreUnexpectedCalls) {
  104. this.$ignoreUnexpectedCalls_ = ignoreUnexpectedCalls;
  105. return this;
  106. };
  107. /** @override */
  108. goog.testing.LooseMock.prototype.$recordExpectation = function() {
  109. if (!this.$expectations_.containsKey(this.$pendingExpectation.name)) {
  110. this.$expectations_.set(
  111. this.$pendingExpectation.name,
  112. new goog.testing.LooseExpectationCollection());
  113. }
  114. var collection = this.$expectations_.get(this.$pendingExpectation.name);
  115. collection.addExpectation(this.$pendingExpectation);
  116. };
  117. /** @override */
  118. goog.testing.LooseMock.prototype.$recordCall = function(name, args) {
  119. if (!this.$expectations_.containsKey(name)) {
  120. if (this.$ignoreUnexpectedCalls_) {
  121. return;
  122. }
  123. this.$throwCallException(name, args);
  124. }
  125. // Start from the beginning of the expectations for this name,
  126. // and iterate over them until we find an expectation that matches
  127. // and also has calls remaining.
  128. var collection = this.$expectations_.get(name);
  129. var matchingExpectation = null;
  130. var expectations = collection.getExpectations();
  131. for (var i = 0; i < expectations.length; i++) {
  132. var expectation = expectations[i];
  133. if (this.$verifyCall(expectation, name, args)) {
  134. matchingExpectation = expectation;
  135. if (expectation.actualCalls < expectation.maxCalls) {
  136. break;
  137. } // else continue and see if we can find something that does match
  138. }
  139. }
  140. if (matchingExpectation == null) {
  141. this.$throwCallException(name, args, expectation);
  142. }
  143. matchingExpectation.actualCalls++;
  144. if (matchingExpectation.actualCalls > matchingExpectation.maxCalls) {
  145. this.$throwException(
  146. 'Too many calls to ' + matchingExpectation.name + '\nExpected: ' +
  147. matchingExpectation.maxCalls + ' but was: ' +
  148. matchingExpectation.actualCalls);
  149. }
  150. this.$calls_.push([name, args]);
  151. return this.$do(matchingExpectation, args);
  152. };
  153. /** @override */
  154. goog.testing.LooseMock.prototype.$reset = function() {
  155. goog.testing.LooseMock.superClass_.$reset.call(this);
  156. this.$expectations_ = new goog.structs.Map();
  157. this.$calls_ = [];
  158. };
  159. /** @override */
  160. goog.testing.LooseMock.prototype.$replay = function() {
  161. goog.testing.LooseMock.superClass_.$replay.call(this);
  162. // Verify that there are no expectations that can never be reached.
  163. // This can't catch every situation, but it is a decent sanity check
  164. // and it's similar to the behavior of EasyMock in java.
  165. var collections = this.$expectations_.getValues();
  166. for (var i = 0; i < collections.length; i++) {
  167. var expectations = collections[i].getExpectations();
  168. for (var j = 0; j < expectations.length; j++) {
  169. var expectation = expectations[j];
  170. // If this expectation can be called infinite times, then
  171. // check if any subsequent expectation has the exact same
  172. // argument list.
  173. if (!isFinite(expectation.maxCalls)) {
  174. for (var k = j + 1; k < expectations.length; k++) {
  175. var laterExpectation = expectations[k];
  176. if (laterExpectation.minCalls > 0 &&
  177. goog.array.equals(
  178. expectation.argumentList, laterExpectation.argumentList)) {
  179. var name = expectation.name;
  180. var argsString = this.$argumentsAsString(expectation.argumentList);
  181. this.$throwException([
  182. 'Expected call to ', name, ' with arguments ', argsString,
  183. ' has an infinite max number of calls; can\'t expect an',
  184. ' identical call later with a positive min number of calls'
  185. ].join(''));
  186. }
  187. }
  188. }
  189. }
  190. }
  191. };
  192. /** @override */
  193. goog.testing.LooseMock.prototype.$verify = function() {
  194. goog.testing.LooseMock.superClass_.$verify.call(this);
  195. var collections = this.$expectations_.getValues();
  196. for (var i = 0; i < collections.length; i++) {
  197. var expectations = collections[i].getExpectations();
  198. for (var j = 0; j < expectations.length; j++) {
  199. var expectation = expectations[j];
  200. if (expectation.actualCalls > expectation.maxCalls) {
  201. this.$throwException(
  202. 'Too many calls to ' + expectation.name + '\nExpected: ' +
  203. expectation.maxCalls + ' but was: ' + expectation.actualCalls);
  204. } else if (expectation.actualCalls < expectation.minCalls) {
  205. this.$throwException(
  206. 'Not enough calls to ' + expectation.name + '\nExpected: ' +
  207. expectation.minCalls + ' but was: ' + expectation.actualCalls);
  208. }
  209. }
  210. }
  211. };