Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
seleniumhq
GitHub Repository: seleniumhq/selenium
Path: blob/trunk/third_party/closure/goog/labs/testing/environment.js
2868 views
1
// Copyright 2014 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
goog.provide('goog.labs.testing.Environment');
16
17
goog.require('goog.Thenable');
18
goog.require('goog.array');
19
goog.require('goog.asserts');
20
goog.require('goog.debug.Console');
21
goog.require('goog.testing.MockClock');
22
goog.require('goog.testing.MockControl');
23
goog.require('goog.testing.PropertyReplacer');
24
goog.require('goog.testing.TestCase');
25
goog.require('goog.testing.jsunit');
26
27
28
/**
29
* JsUnit environments allow developers to customize the existing testing
30
* lifecycle by hitching additional setUp and tearDown behaviors to tests.
31
*
32
* Environments will run their setUp steps in the order in which they
33
* are instantiated and registered. During tearDown, the environments will
34
* unwind the setUp and execute in reverse order.
35
*
36
* See http://go/jsunit-env for more information.
37
*/
38
goog.labs.testing.Environment = goog.defineClass(null, {
39
/** @constructor */
40
constructor: function() {
41
var testcase = goog.labs.testing.EnvironmentTestCase_.getInstance();
42
testcase.registerEnvironment_(this);
43
44
// Record the active test case, in normal usage this is a singleton,
45
// but while testing this case it is reset.
46
goog.labs.testing.Environment.activeTestCase_ = testcase;
47
48
/** @type {goog.testing.MockControl} */
49
this.mockControl = null;
50
51
/** @type {goog.testing.MockClock} */
52
this.mockClock = null;
53
54
/** @private {boolean} */
55
this.shouldMakeMockControl_ = false;
56
57
/** @private {boolean} */
58
this.shouldMakeMockClock_ = false;
59
60
/** @const {!goog.debug.Console} */
61
this.console = goog.labs.testing.Environment.console_;
62
63
/** @const {!goog.testing.PropertyReplacer} */
64
this.replacer = new goog.testing.PropertyReplacer();
65
},
66
67
68
/** Runs immediately before the setUpPage phase of JsUnit tests. */
69
setUpPage: function() {
70
if (this.mockClock && this.mockClock.isDisposed()) {
71
this.mockClock = new goog.testing.MockClock(true);
72
}
73
},
74
75
76
/** Runs immediately after the tearDownPage phase of JsUnit tests. */
77
tearDownPage: function() {
78
// If we created the mockClock, we'll also dispose it.
79
if (this.shouldMakeMockClock_) {
80
this.mockClock.dispose();
81
}
82
},
83
84
/** Runs immediately before the setUp phase of JsUnit tests. */
85
setUp: goog.nullFunction,
86
87
/** Runs immediately after the tearDown phase of JsUnit tests. */
88
tearDown: function() {
89
// Make sure promises and other stuff that may still be scheduled, get a
90
// chance to run (and throw errors).
91
if (this.mockClock) {
92
for (var i = 0; i < 100; i++) {
93
this.mockClock.tick(1000);
94
}
95
// If we created the mockClock, we'll also reset it.
96
if (this.shouldMakeMockClock_) {
97
this.mockClock.reset();
98
}
99
}
100
// Reset all changes made by the PropertyReplacer.
101
this.replacer.reset();
102
// Make sure the user did not forget to call $replayAll & $verifyAll in
103
// their test. This is a noop if they did.
104
// This is important because:
105
// - Engineers thinks that not all their tests need to replay and verify.
106
// That lets tests sneak in that call mocks but never replay those calls.
107
// - Then some well meaning maintenance engineer wants to update the test
108
// with some new mock, adds a replayAll and BOOM the test fails
109
// because completely unrelated mocks now get replayed.
110
if (this.mockControl) {
111
try {
112
this.mockControl.$verifyAll();
113
this.mockControl.$replayAll();
114
this.mockControl.$verifyAll();
115
} finally {
116
this.mockControl.$resetAll();
117
if (this.shouldMakeMockControl_) {
118
// If we created the mockControl, we'll also tear it down.
119
this.mockControl.$tearDown();
120
}
121
}
122
}
123
// Verifying the mockControl may throw, so if cleanup needs to happen,
124
// add it further up in the function.
125
},
126
127
128
/**
129
* Create a new {@see goog.testing.MockControl} accessible via
130
* {@code env.mockControl} for each test. If your test has more than one
131
* testing environment, don't call this on more than one of them.
132
* @return {!goog.labs.testing.Environment} For chaining.
133
*/
134
withMockControl: function() {
135
if (!this.shouldMakeMockControl_) {
136
this.shouldMakeMockControl_ = true;
137
this.mockControl = new goog.testing.MockControl();
138
}
139
return this;
140
},
141
142
143
/**
144
* Create a {@see goog.testing.MockClock} for each test. The clock will be
145
* installed (override i.e. setTimeout) by default. It can be accessed
146
* using {@code env.mockClock}. If your test has more than one testing
147
* environment, don't call this on more than one of them.
148
* @return {!goog.labs.testing.Environment} For chaining.
149
*/
150
withMockClock: function() {
151
if (!this.shouldMakeMockClock_) {
152
this.shouldMakeMockClock_ = true;
153
this.mockClock = new goog.testing.MockClock(true);
154
}
155
return this;
156
},
157
158
159
/**
160
* Creates a basic strict mock of a {@code toMock}. For more advanced mocking,
161
* please use the MockControl directly.
162
* @param {Function} toMock
163
* @return {!goog.testing.StrictMock}
164
*/
165
mock: function(toMock) {
166
if (!this.shouldMakeMockControl_) {
167
throw new Error(
168
'MockControl not available on this environment. ' +
169
'Call withMockControl if this environment is expected ' +
170
'to contain a MockControl.');
171
}
172
return this.mockControl.createStrictMock(toMock);
173
}
174
});
175
176
177
/**
178
* @private {?goog.testing.TestCase}
179
*/
180
goog.labs.testing.Environment.activeTestCase_ = null;
181
182
183
// TODO(johnlenz): make this package private when it moves out of labs.
184
/**
185
* @return {?goog.testing.TestCase}
186
*/
187
goog.labs.testing.Environment.getTestCaseIfActive = function() {
188
return goog.labs.testing.Environment.activeTestCase_;
189
};
190
191
192
/** @private @const {!goog.debug.Console} */
193
goog.labs.testing.Environment.console_ = new goog.debug.Console();
194
195
196
// Activate logging to the browser's console by default.
197
goog.labs.testing.Environment.console_.setCapturing(true);
198
199
200
201
/**
202
* An internal TestCase used to hook environments into the JsUnit test runner.
203
* Environments cannot be used in conjunction with custom TestCases for JsUnit.
204
* @private @final @constructor
205
* @extends {goog.testing.TestCase}
206
*/
207
goog.labs.testing.EnvironmentTestCase_ = function() {
208
goog.labs.testing.EnvironmentTestCase_.base(this, 'constructor');
209
210
/** @private {!Array<!goog.labs.testing.Environment>}> */
211
this.environments_ = [];
212
213
/** @private {!Object} */
214
this.testobj_ = goog.global; // default
215
216
// Automatically install this TestCase when any environment is used in a test.
217
goog.testing.TestCase.initializeTestRunner(this);
218
};
219
goog.inherits(goog.labs.testing.EnvironmentTestCase_, goog.testing.TestCase);
220
goog.addSingletonGetter(goog.labs.testing.EnvironmentTestCase_);
221
222
223
/**
224
* @param {!Object} obj An object providing the test and life cycle methods.
225
* @override
226
*/
227
goog.labs.testing.EnvironmentTestCase_.prototype.setTestObj = function(obj) {
228
goog.asserts.assert(
229
this.testobj_ == goog.global,
230
'A test method object has already been provided ' +
231
'and only one is supported.');
232
this.testobj_ = obj;
233
goog.labs.testing.EnvironmentTestCase_.base(this, 'setTestObj', obj);
234
};
235
236
237
/**
238
* Override the default global scope discovery of lifecycle functions to prevent
239
* overriding the custom environment setUp(Page)/tearDown(Page) logic.
240
* @override
241
*/
242
goog.labs.testing.EnvironmentTestCase_.prototype.autoDiscoverLifecycle =
243
function() {
244
if (this.testobj_['runTests']) {
245
this.runTests = goog.bind(this.testobj_['runTests'], this.testobj_);
246
}
247
if (this.testobj_['shouldRunTests']) {
248
this.shouldRunTests =
249
goog.bind(this.testobj_['shouldRunTests'], this.testobj_);
250
}
251
};
252
253
254
/**
255
* Adds an environment to the JsUnit test.
256
* @param {!goog.labs.testing.Environment} env
257
* @private
258
*/
259
goog.labs.testing.EnvironmentTestCase_.prototype.registerEnvironment_ =
260
function(env) {
261
this.environments_.push(env);
262
};
263
264
265
/** @override */
266
goog.labs.testing.EnvironmentTestCase_.prototype.setUpPage = function() {
267
var setUpPageFns = goog.array.map(this.environments_, function(env) {
268
return goog.bind(env.setUpPage, env);
269
}, this);
270
271
// User defined setUpPage method.
272
if (this.testobj_['setUpPage']) {
273
setUpPageFns.push(goog.bind(this.testobj_['setUpPage'], this.testobj_));
274
}
275
return this.callAndChainPromises_(setUpPageFns);
276
};
277
278
279
/** @override */
280
goog.labs.testing.EnvironmentTestCase_.prototype.setUp = function() {
281
var setUpFns = [];
282
// User defined configure method.
283
if (this.testobj_['configureEnvironment']) {
284
setUpFns.push(
285
goog.bind(this.testobj_['configureEnvironment'], this.testobj_));
286
}
287
288
goog.array.forEach(this.environments_, function(env) {
289
setUpFns.push(goog.bind(env.setUp, env));
290
}, this);
291
292
// User defined setUp method.
293
if (this.testobj_['setUp']) {
294
setUpFns.push(goog.bind(this.testobj_['setUp'], this.testobj_));
295
}
296
return this.callAndChainPromises_(setUpFns);
297
};
298
299
300
/**
301
* Calls a chain of methods and makes sure to properly chain them if any of the
302
* methods returns a thenable.
303
* @param {!Array<function()>} fns
304
* @return {!goog.Thenable|undefined}
305
* @private
306
*/
307
goog.labs.testing.EnvironmentTestCase_.prototype.callAndChainPromises_ =
308
function(fns) {
309
return goog.array.reduce(fns, function(previousResult, fn) {
310
if (goog.Thenable.isImplementedBy(previousResult)) {
311
return previousResult.then(function() {
312
return fn();
313
});
314
}
315
return fn();
316
}, undefined /* initialValue */, this);
317
};
318
319
320
/** @override */
321
goog.labs.testing.EnvironmentTestCase_.prototype.tearDown = function() {
322
var firstException;
323
// User defined tearDown method.
324
if (this.testobj_['tearDown']) {
325
try {
326
this.testobj_['tearDown']();
327
} catch (e) {
328
if (!firstException) {
329
firstException = e || new Error('Exception thrown: ' + String(e));
330
}
331
}
332
}
333
334
// Execute the tearDown methods for the environment in the reverse order
335
// in which they were registered to "unfold" the setUp.
336
goog.array.forEachRight(this.environments_, function(env) {
337
// For tearDowns between tests make sure they run as much as possible to
338
// avoid interference between tests.
339
try {
340
env.tearDown();
341
} catch (e) {
342
if (!firstException) {
343
firstException = e || new Error('Exception thrown: ' + String(e));
344
}
345
}
346
});
347
if (firstException) {
348
throw firstException;
349
}
350
};
351
352
353
/** @override */
354
goog.labs.testing.EnvironmentTestCase_.prototype.tearDownPage = function() {
355
// User defined tearDownPage method.
356
if (this.testobj_['tearDownPage']) {
357
this.testobj_['tearDownPage']();
358
}
359
360
goog.array.forEachRight(
361
this.environments_, function(env) { env.tearDownPage(); });
362
};
363
364