// Copyright 2010 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 A wrapper for MockControl that provides mocks and assertions * for testing asynchronous code. All assertions will only be verified when * $verifyAll is called on the wrapped MockControl. * * This class is meant primarily for testing code that exposes asynchronous APIs * without being truly asynchronous (using asynchronous primitives like browser * events or timeouts). This is often the case when true asynchronous * depedencies have been mocked out. This means that it doesn't rely on * AsyncTestCase or DeferredTestCase, although it can be used with those as * well. * * Example usage: * *
 * var mockControl = new goog.testing.MockControl();
 * var asyncMockControl = new goog.testing.async.MockControl(mockControl);
 *
 * myAsyncObject.onSuccess(asyncMockControl.asyncAssertEquals(
 *     'callback should run and pass the correct value',
 *     'http://someurl.com');
 * asyncMockControl.assertDeferredEquals(
 *     'deferred object should be resolved with the correct value',
 *     'http://someurl.com',
 *     myAsyncObject.getDeferredUrl());
 * asyncMockControl.run();
 * mockControl.$verifyAll();
 * 
* */ goog.setTestOnly('goog.testing.async.MockControl'); goog.provide('goog.testing.async.MockControl'); goog.require('goog.asserts'); goog.require('goog.async.Deferred'); goog.require('goog.debug'); goog.require('goog.testing.asserts'); goog.require('goog.testing.mockmatchers.IgnoreArgument'); /** * Provides asynchronous mocks and assertions controlled by a parent * MockControl. * * @param {goog.testing.MockControl} mockControl The parent MockControl. * @constructor * @final */ goog.testing.async.MockControl = function(mockControl) { /** * The parent MockControl. * @type {goog.testing.MockControl} * @private */ this.mockControl_ = mockControl; }; /** * Returns a function that will assert that it will be called, and run the given * callback when it is. * * @param {string} name The name of the callback mock. * @param {function(...*) : *} callback The wrapped callback. This will be * called when the returned function is called. * @param {Object=} opt_selfObj The object which this should point to when the * callback is run. * @return {!Function} The mock callback. * @suppress {missingProperties} Mocks do not fit in the type system well. */ goog.testing.async.MockControl.prototype.createCallbackMock = function( name, callback, opt_selfObj) { goog.asserts.assert( goog.isString(name), 'name parameter ' + goog.debug.deepExpose(name) + ' should be a string'); var ignored = new goog.testing.mockmatchers.IgnoreArgument(); // Use everyone's favorite "double-cast" trick to subvert the type system. var obj = /** @type {Object} */ (this.mockControl_.createFunctionMock(name)); var fn = /** @type {Function} */ (obj); fn(ignored).$does(function(args) { if (opt_selfObj) { callback = goog.bind(callback, opt_selfObj); } return callback.apply(this, args); }); fn.$replay(); return function() { return fn(arguments); }; }; /** * Returns a function that will assert that its arguments are equal to the * arguments given to asyncAssertEquals. In addition, the function also asserts * that it will be called. * * @param {string} message A message to print if the arguments are wrong. * @param {...*} var_args The arguments to assert. * @return {function(...*) : void} The mock callback. */ goog.testing.async.MockControl.prototype.asyncAssertEquals = function( message, var_args) { var expectedArgs = Array.prototype.slice.call(arguments, 1); return this.createCallbackMock('asyncAssertEquals', function() { assertObjectEquals( message, expectedArgs, Array.prototype.slice.call(arguments)); }); }; /** * Asserts that a deferred object will have an error and call its errback * function. * @param {goog.async.Deferred} deferred The deferred object. * @param {function() : void} fn A function wrapping the code in which the error * will occur. */ goog.testing.async.MockControl.prototype.assertDeferredError = function( deferred, fn) { deferred.addErrback( this.createCallbackMock('assertDeferredError', function() {})); goog.testing.asserts.callWithoutLogging(fn); }; /** * Asserts that a deferred object will call its callback with the given value. * * @param {string} message A message to print if the arguments are wrong. * @param {goog.async.Deferred|*} expected The expected value. If this is a * deferred object, then the expected value is the deferred value. * @param {goog.async.Deferred|*} actual The actual value. If this is a deferred * object, then the actual value is the deferred value. Either this or * 'expected' must be deferred. */ goog.testing.async.MockControl.prototype.assertDeferredEquals = function( message, expected, actual) { if (expected instanceof goog.async.Deferred && actual instanceof goog.async.Deferred) { // Assert that the first deferred is resolved. expected.addCallback( this.createCallbackMock('assertDeferredEquals', function(exp) { // Assert that the second deferred is resolved, and that the value is // as expected. actual.addCallback(this.asyncAssertEquals(message, exp)); }, this)); } else if (expected instanceof goog.async.Deferred) { expected.addCallback( this.createCallbackMock('assertDeferredEquals', function(exp) { assertObjectEquals(message, exp, actual); })); } else if (actual instanceof goog.async.Deferred) { actual.addCallback(this.asyncAssertEquals(message, expected)); } else { throw Error('Either expected or actual must be deferred'); } };