diff --git a/CHANGELOG.md b/CHANGELOG.md index 8c4024dfd..0af831f56 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -66,7 +66,37 @@ casper.test.begin('Casperjs.org is navigable', 2, function suite(test) { }); ``` -`Tester#begin()` has also `setUp()` and `tearDown()` capabilities if you pass it a configuration object instead of a function: +[`Tester#setUp()`](docs.casperjs.org/en/latest/modules/tester.html#setup) and [`Tester#tearDown()`](docs.casperjs.org/en/latest/modules/tester.html#teardown) methods have been also added in order to ease the definition of operations to be performed before and after each test defined using `Tester#begin()`: + +```js +casper.test.setUp(function() { + console.log('executed before each test'); +}); + +casper.test.tearDown(function() { + console.log('executed after each test'); +}); +``` + +Both can work asynchronously as well of you define the `done` argument: + +```js +casper.test.setUp(function(done) { + setTimeout(function() { + console.log('asynchronously executed before each test'); + done(); + }, 1000); +}); + +casper.test.tearDown(function(done) { + setTimeout(function() { + console.log('asynchronously executed after each test'); + done(); + }, 1000); +}); +``` + +`Tester#begin()` itself has also local `setUp()` and `tearDown()` capabilities if you pass it a configuration object instead of a function: ```js casper.test.begin('range tests', 1, { @@ -75,9 +105,11 @@ casper.test.begin('range tests', 1, { setUp: function(test) { this.range.push(3); }, + tearDown: function(test) { range = []; }, + test: function(test) { test.assertEquals(range.length, 3); test.done(); @@ -85,7 +117,7 @@ casper.test.begin('range tests', 1, { }); ``` -Also, scraping and testing are now betterly separated in CasperJS, and bad code is now a bit less bad. That involves breaking up BC on some points though: +Scraping and testing are now better separated in CasperJS. That involves breaking up BC on some points though: - The Casper object won't be created with a `test` reference if not invoked using the [`casperjs test` command](http://casperjs.org/testing.html#casper-test-command), therefore the ability to run any test without calling it has been dropped. I know, get over it. - Passing the planned number of tests to `casper.done()` has been dropped as well, because `done()` may be never called at all when big troubles happen; rather use the new `begin()` method and provide the expected number of tests using the second argument: diff --git a/docs/modules/tester.rst b/docs/modules/tester.rst index 22af2ecf1..83816bf60 100644 --- a/docs/modules/tester.rst +++ b/docs/modules/tester.rst @@ -761,6 +761,31 @@ Render test results, save results in an XUnit formatted file, and optionally exi This method is not to be called when using the ``casperjs test`` command (see documentation for :doc:`testing <../testing>`), where it's done automatically for you. +``setUp()`` +------------------------------------------------------------------------------- + +**Signature:** ``setUp([Function fn])`` + +Defines a function which will be executed before every test defined using `begin()`_:: + + casper.test.setUp(function() { + casper.start().userAgent('Mosaic 0.1'); + }); + +To perform asynchronous operations, use the ``done`` argument:: + + casper.test.setUp(function(done) { + casper.start('http://foo').then(function() { + // ... + }).run(done); + }); + +.. warning:: + + Don't specify the ``done`` argument if you don't intend to use the method asynchronously. + +.. seealso:: `tearDown()`_ + ``skip()`` ------------------------------------------------------------------------------- @@ -774,3 +799,28 @@ Skips a given number of planned tests:: test.skip(2, 'Two tests skipped'); test.done(); }); + +``tearDown()`` +------------------------------------------------------------------------------- + +**Signature:** ``tearDown([Function fn])`` + +Defines a function which will be executed before after every test defined using `begin()`_:: + + casper.test.tearDown(function() { + casper.echo('See ya'); + }); + +To perform asynchronous operations, use the ``done`` argument:: + + casper.test.tearDown(function(done) { + casper.start('http://foo/goodbye').then(function() { + // ... + }).run(done); + }); + +.. warning:: + + Don't specify the ``done`` argument if you don't intend to use the method asynchronously. + +.. seealso:: `setUp()`_ diff --git a/modules/tester.js b/modules/tester.js index 5f953d946..b4be10313 100755 --- a/modules/tester.js +++ b/modules/tester.js @@ -96,6 +96,8 @@ var Tester = function Tester(casper, options) { this.casper = casper; // public properties + this._setUp = undefined; + this._tearDown = undefined; this.aborted = false; this.executed = 0; this.currentTestFile = null; @@ -892,27 +894,44 @@ Tester.prototype.bar = function bar(text, style) { }; /** - * Starts a suite. + * Defines a function which will be executed before every test. * - * Can be invoked two different ways: + * @param Function fn + */ +Tester.prototype.setUp = function setUp(fn) { + "use strict"; + this._setUp = fn; +}; + +/** + * Defines a function which will be executed after every test. * - * casper.test.begin("suite description", plannedTests, function(test){}) + * @param Function fn + */ +Tester.prototype.tearDown = function tearDown(fn) { + "use strict"; + this._tearDown = fn; +}; + +/** + * Starts a suite. * - * Or: + * Can be invoked different ways: * + * casper.test.begin("suite description", plannedTests, function(test){}) * casper.test.begin("suite description", function(test){}) - * */ Tester.prototype.begin = function begin() { "use strict"; - if (this.started && this.running) { + if (this.started && this.running) return this.queue.push(arguments); - } + function getConfig(args) { var config = { setUp: function(){}, tearDown: function(){} }; + if (utils.isFunction(args[1])) { config.test = args[1]; } else if (utils.isObject(args[1])) { @@ -926,42 +945,56 @@ Tester.prototype.begin = function begin() { } else { throw new CasperError('Invalid call'); } - if (!utils.isFunction(config.test)) { + + if (!utils.isFunction(config.test)) throw new CasperError('begin() is missing a mandatory test function'); - } + return config; } + var description = arguments[0] || f("Untitled suite in %s", this.currentTestFile), - config = getConfig([].slice.call(arguments)); - if (!this.options.concise) { + config = getConfig([].slice.call(arguments)), + next = function() { + config.test(this, this.casper); + if (this.options.concise) + this.casper.echo([ + this.colorize('PASS', 'INFO'), + this.formatMessage(description), + this.colorize(f('(%d test%s)', + config.planned, + config.planned > 1 ? 's' : ''), 'INFO') + ].join(' ')); + }.bind(this); + + if (!this.options.concise) this.comment(description); - } + this.currentSuite = new TestCaseResult({ name: description, file: this.currentTestFile, config: config, planned: config.planned || undefined }); + this.executed = 0; this.running = this.started = true; + try { - if (config.setUp) { + if (config.setUp) config.setUp(this, this.casper); - } - config.test(this, this.casper); + + if (!this._setUp) + return next(); + + if (this._setUp.length > 0) + return this._setUp.call(this, next); // async + + this._setUp.call(this); // sync + next(); } catch (err) { this.processError(err); this.done(); } - if (this.options.concise) { - this.casper.echo([ - this.colorize('PASS', 'INFO'), - this.formatMessage(description), - this.colorize(f('(%d test%s)', - config.planned, - config.planned > 1 ? 's' : ''), 'INFO') - ].join(' ')); - } }; /** @@ -995,10 +1028,12 @@ Tester.prototype.done = function done() { "use strict"; /*jshint maxstatements:20, maxcomplexity:20*/ var planned, config = this.currentSuite && this.currentSuite.config || {}; + if (utils.isNumber(arguments[0])) { this.casper.warn('done() `planned` arg is deprecated as of 1.1'); planned = arguments[0]; } + if (config && config.tearDown && utils.isFunction(config.tearDown)) { try { config.tearDown(this, this.casper); @@ -1006,25 +1041,45 @@ Tester.prototype.done = function done() { this.processError(error); } } - if (this.currentSuite && this.currentSuite.planned && - this.currentSuite.planned !== this.executed + this.currentSuite.skipped && - !this.currentSuite.failed) { - this.dubious(this.currentSuite.planned, this.executed, this.currentSuite.name); - } else if (planned && planned !== this.executed) { - // BC - this.dubious(planned, this.executed); - } - if (this.currentSuite) { - this.suiteResults.push(this.currentSuite); - this.currentSuite = undefined; - this.executed = 0; + + var next = function() { + if (this.currentSuite && this.currentSuite.planned && + this.currentSuite.planned !== this.executed + this.currentSuite.skipped && + !this.currentSuite.failed) { + this.dubious(this.currentSuite.planned, this.executed, this.currentSuite.name); + } else if (planned && planned !== this.executed) { + // BC + this.dubious(planned, this.executed); + } + if (this.currentSuite) { + this.suiteResults.push(this.currentSuite); + this.currentSuite = undefined; + this.executed = 0; + } + this.emit('test.done'); + this.casper.currentHTTPResponse = {}; + this.running = this.started = false; + var nextTest = this.queue.shift(); + if (nextTest) { + this.begin.apply(this, nextTest); + } + }.bind(this); + + if (!this._tearDown) { + return next(); } - this.emit('test.done'); - this.casper.currentHTTPResponse = {}; - this.running = this.started = false; - var nextTest = this.queue.shift(); - if (nextTest) { - this.begin.apply(this, nextTest); + + try { + if (this._tearDown.length > 0) { + // async + this._tearDown.call(this, next); + } else { + // sync + this._tearDown.call(this); + next(); + } + } catch (error) { + this.processError(error); } }; diff --git a/tests/suites/tester/setup-teardown-async.js b/tests/suites/tester/setup-teardown-async.js new file mode 100644 index 000000000..4db990f41 --- /dev/null +++ b/tests/suites/tester/setup-teardown-async.js @@ -0,0 +1,31 @@ +/*global casper*/ +/*jshint strict:false*/ + +var setUp, tearDown; + +casper.test.setUp(function(done) { + setTimeout(function() { + setUp = true; + done(); + }, 50); +}); + +casper.test.tearDown(function(done) { + setTimeout(function() { + tearDown = true; + done(); + }, 50); +}); + +casper.test.begin('setUp() tests', 1, function(test) { + test.assertTrue(setUp, 'Tester.setUp() executed the async setup function'); + test.done(); +}); + +casper.test.begin('tearDown() tests', 1, function(test) { + test.assertTrue(tearDown, 'Tester.tearDown() executed the async tear down function'); + // reset + test.setUp(); + test.tearDown(); + test.done(); +}); diff --git a/tests/suites/tester/setup-teardown.js b/tests/suites/tester/setup-teardown.js new file mode 100644 index 000000000..330870ba3 --- /dev/null +++ b/tests/suites/tester/setup-teardown.js @@ -0,0 +1,25 @@ +/*global casper*/ +/*jshint strict:false*/ + +var setUp, tearDown; + +casper.test.setUp(function() { + setUp = true; +}); + +casper.test.tearDown(function() { + tearDown = true; +}); + +casper.test.begin('setUp() tests', 1, function(test) { + test.assertTrue(setUp, 'Tester.setUp() executed the setup function'); + test.done(); +}); + +casper.test.begin('tearDown() tests', 1, function(test) { + test.assertTrue(tearDown, 'Tester.tearDown() executed the tear down function'); + // reset + test.setUp(); + test.tearDown(); + test.done(); +});