Path: blob/trunk/third_party/closure/goog/labs/testing/environment.js
2868 views
// Copyright 2014 The Closure Library Authors. All Rights Reserved.1//2// Licensed under the Apache License, Version 2.0 (the "License");3// you may not use this file except in compliance with the License.4// You may obtain a copy of the License at5//6// http://www.apache.org/licenses/LICENSE-2.07//8// Unless required by applicable law or agreed to in writing, software9// distributed under the License is distributed on an "AS-IS" BASIS,10// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.11// See the License for the specific language governing permissions and12// limitations under the License.1314goog.provide('goog.labs.testing.Environment');1516goog.require('goog.Thenable');17goog.require('goog.array');18goog.require('goog.asserts');19goog.require('goog.debug.Console');20goog.require('goog.testing.MockClock');21goog.require('goog.testing.MockControl');22goog.require('goog.testing.PropertyReplacer');23goog.require('goog.testing.TestCase');24goog.require('goog.testing.jsunit');252627/**28* JsUnit environments allow developers to customize the existing testing29* lifecycle by hitching additional setUp and tearDown behaviors to tests.30*31* Environments will run their setUp steps in the order in which they32* are instantiated and registered. During tearDown, the environments will33* unwind the setUp and execute in reverse order.34*35* See http://go/jsunit-env for more information.36*/37goog.labs.testing.Environment = goog.defineClass(null, {38/** @constructor */39constructor: function() {40var testcase = goog.labs.testing.EnvironmentTestCase_.getInstance();41testcase.registerEnvironment_(this);4243// Record the active test case, in normal usage this is a singleton,44// but while testing this case it is reset.45goog.labs.testing.Environment.activeTestCase_ = testcase;4647/** @type {goog.testing.MockControl} */48this.mockControl = null;4950/** @type {goog.testing.MockClock} */51this.mockClock = null;5253/** @private {boolean} */54this.shouldMakeMockControl_ = false;5556/** @private {boolean} */57this.shouldMakeMockClock_ = false;5859/** @const {!goog.debug.Console} */60this.console = goog.labs.testing.Environment.console_;6162/** @const {!goog.testing.PropertyReplacer} */63this.replacer = new goog.testing.PropertyReplacer();64},656667/** Runs immediately before the setUpPage phase of JsUnit tests. */68setUpPage: function() {69if (this.mockClock && this.mockClock.isDisposed()) {70this.mockClock = new goog.testing.MockClock(true);71}72},737475/** Runs immediately after the tearDownPage phase of JsUnit tests. */76tearDownPage: function() {77// If we created the mockClock, we'll also dispose it.78if (this.shouldMakeMockClock_) {79this.mockClock.dispose();80}81},8283/** Runs immediately before the setUp phase of JsUnit tests. */84setUp: goog.nullFunction,8586/** Runs immediately after the tearDown phase of JsUnit tests. */87tearDown: function() {88// Make sure promises and other stuff that may still be scheduled, get a89// chance to run (and throw errors).90if (this.mockClock) {91for (var i = 0; i < 100; i++) {92this.mockClock.tick(1000);93}94// If we created the mockClock, we'll also reset it.95if (this.shouldMakeMockClock_) {96this.mockClock.reset();97}98}99// Reset all changes made by the PropertyReplacer.100this.replacer.reset();101// Make sure the user did not forget to call $replayAll & $verifyAll in102// their test. This is a noop if they did.103// This is important because:104// - Engineers thinks that not all their tests need to replay and verify.105// That lets tests sneak in that call mocks but never replay those calls.106// - Then some well meaning maintenance engineer wants to update the test107// with some new mock, adds a replayAll and BOOM the test fails108// because completely unrelated mocks now get replayed.109if (this.mockControl) {110try {111this.mockControl.$verifyAll();112this.mockControl.$replayAll();113this.mockControl.$verifyAll();114} finally {115this.mockControl.$resetAll();116if (this.shouldMakeMockControl_) {117// If we created the mockControl, we'll also tear it down.118this.mockControl.$tearDown();119}120}121}122// Verifying the mockControl may throw, so if cleanup needs to happen,123// add it further up in the function.124},125126127/**128* Create a new {@see goog.testing.MockControl} accessible via129* {@code env.mockControl} for each test. If your test has more than one130* testing environment, don't call this on more than one of them.131* @return {!goog.labs.testing.Environment} For chaining.132*/133withMockControl: function() {134if (!this.shouldMakeMockControl_) {135this.shouldMakeMockControl_ = true;136this.mockControl = new goog.testing.MockControl();137}138return this;139},140141142/**143* Create a {@see goog.testing.MockClock} for each test. The clock will be144* installed (override i.e. setTimeout) by default. It can be accessed145* using {@code env.mockClock}. If your test has more than one testing146* environment, don't call this on more than one of them.147* @return {!goog.labs.testing.Environment} For chaining.148*/149withMockClock: function() {150if (!this.shouldMakeMockClock_) {151this.shouldMakeMockClock_ = true;152this.mockClock = new goog.testing.MockClock(true);153}154return this;155},156157158/**159* Creates a basic strict mock of a {@code toMock}. For more advanced mocking,160* please use the MockControl directly.161* @param {Function} toMock162* @return {!goog.testing.StrictMock}163*/164mock: function(toMock) {165if (!this.shouldMakeMockControl_) {166throw new Error(167'MockControl not available on this environment. ' +168'Call withMockControl if this environment is expected ' +169'to contain a MockControl.');170}171return this.mockControl.createStrictMock(toMock);172}173});174175176/**177* @private {?goog.testing.TestCase}178*/179goog.labs.testing.Environment.activeTestCase_ = null;180181182// TODO(johnlenz): make this package private when it moves out of labs.183/**184* @return {?goog.testing.TestCase}185*/186goog.labs.testing.Environment.getTestCaseIfActive = function() {187return goog.labs.testing.Environment.activeTestCase_;188};189190191/** @private @const {!goog.debug.Console} */192goog.labs.testing.Environment.console_ = new goog.debug.Console();193194195// Activate logging to the browser's console by default.196goog.labs.testing.Environment.console_.setCapturing(true);197198199200/**201* An internal TestCase used to hook environments into the JsUnit test runner.202* Environments cannot be used in conjunction with custom TestCases for JsUnit.203* @private @final @constructor204* @extends {goog.testing.TestCase}205*/206goog.labs.testing.EnvironmentTestCase_ = function() {207goog.labs.testing.EnvironmentTestCase_.base(this, 'constructor');208209/** @private {!Array<!goog.labs.testing.Environment>}> */210this.environments_ = [];211212/** @private {!Object} */213this.testobj_ = goog.global; // default214215// Automatically install this TestCase when any environment is used in a test.216goog.testing.TestCase.initializeTestRunner(this);217};218goog.inherits(goog.labs.testing.EnvironmentTestCase_, goog.testing.TestCase);219goog.addSingletonGetter(goog.labs.testing.EnvironmentTestCase_);220221222/**223* @param {!Object} obj An object providing the test and life cycle methods.224* @override225*/226goog.labs.testing.EnvironmentTestCase_.prototype.setTestObj = function(obj) {227goog.asserts.assert(228this.testobj_ == goog.global,229'A test method object has already been provided ' +230'and only one is supported.');231this.testobj_ = obj;232goog.labs.testing.EnvironmentTestCase_.base(this, 'setTestObj', obj);233};234235236/**237* Override the default global scope discovery of lifecycle functions to prevent238* overriding the custom environment setUp(Page)/tearDown(Page) logic.239* @override240*/241goog.labs.testing.EnvironmentTestCase_.prototype.autoDiscoverLifecycle =242function() {243if (this.testobj_['runTests']) {244this.runTests = goog.bind(this.testobj_['runTests'], this.testobj_);245}246if (this.testobj_['shouldRunTests']) {247this.shouldRunTests =248goog.bind(this.testobj_['shouldRunTests'], this.testobj_);249}250};251252253/**254* Adds an environment to the JsUnit test.255* @param {!goog.labs.testing.Environment} env256* @private257*/258goog.labs.testing.EnvironmentTestCase_.prototype.registerEnvironment_ =259function(env) {260this.environments_.push(env);261};262263264/** @override */265goog.labs.testing.EnvironmentTestCase_.prototype.setUpPage = function() {266var setUpPageFns = goog.array.map(this.environments_, function(env) {267return goog.bind(env.setUpPage, env);268}, this);269270// User defined setUpPage method.271if (this.testobj_['setUpPage']) {272setUpPageFns.push(goog.bind(this.testobj_['setUpPage'], this.testobj_));273}274return this.callAndChainPromises_(setUpPageFns);275};276277278/** @override */279goog.labs.testing.EnvironmentTestCase_.prototype.setUp = function() {280var setUpFns = [];281// User defined configure method.282if (this.testobj_['configureEnvironment']) {283setUpFns.push(284goog.bind(this.testobj_['configureEnvironment'], this.testobj_));285}286287goog.array.forEach(this.environments_, function(env) {288setUpFns.push(goog.bind(env.setUp, env));289}, this);290291// User defined setUp method.292if (this.testobj_['setUp']) {293setUpFns.push(goog.bind(this.testobj_['setUp'], this.testobj_));294}295return this.callAndChainPromises_(setUpFns);296};297298299/**300* Calls a chain of methods and makes sure to properly chain them if any of the301* methods returns a thenable.302* @param {!Array<function()>} fns303* @return {!goog.Thenable|undefined}304* @private305*/306goog.labs.testing.EnvironmentTestCase_.prototype.callAndChainPromises_ =307function(fns) {308return goog.array.reduce(fns, function(previousResult, fn) {309if (goog.Thenable.isImplementedBy(previousResult)) {310return previousResult.then(function() {311return fn();312});313}314return fn();315}, undefined /* initialValue */, this);316};317318319/** @override */320goog.labs.testing.EnvironmentTestCase_.prototype.tearDown = function() {321var firstException;322// User defined tearDown method.323if (this.testobj_['tearDown']) {324try {325this.testobj_['tearDown']();326} catch (e) {327if (!firstException) {328firstException = e || new Error('Exception thrown: ' + String(e));329}330}331}332333// Execute the tearDown methods for the environment in the reverse order334// in which they were registered to "unfold" the setUp.335goog.array.forEachRight(this.environments_, function(env) {336// For tearDowns between tests make sure they run as much as possible to337// avoid interference between tests.338try {339env.tearDown();340} catch (e) {341if (!firstException) {342firstException = e || new Error('Exception thrown: ' + String(e));343}344}345});346if (firstException) {347throw firstException;348}349};350351352/** @override */353goog.labs.testing.EnvironmentTestCase_.prototype.tearDownPage = function() {354// User defined tearDownPage method.355if (this.testobj_['tearDownPage']) {356this.testobj_['tearDownPage']();357}358359goog.array.forEachRight(360this.environments_, function(env) { env.tearDownPage(); });361};362363364