diff --git a/lib/test.js b/lib/test.js index da8dd3cf..4c79f0b3 100644 --- a/lib/test.js +++ b/lib/test.js @@ -79,8 +79,8 @@ Test.prototype.serverAddress = function(app, path) { */ Test.prototype.expect = function(a, b, c) { - // callback - if (typeof a === 'function') { + // callback or promise + if (typeof a === 'function' || typeof a.then === 'function') { this._asserts.push(a); return this; } @@ -145,7 +145,7 @@ Test.prototype.end = function(fn) { */ Test.prototype.assert = function(resError, res, fn) { - var error; + var maybePromise; var i; // check for unexpected network errors or server not running/reachable errors @@ -161,22 +161,58 @@ Test.prototype.assert = function(resError, res, fn) { if (!res && resError && (resError instanceof Error) && (resError.syscall === 'connect') && (Object.getOwnPropertyNames(sysErrors).indexOf(resError.code) >= 0)) { - error = new Error(resError.code + ': ' + sysErrors[resError.code]); - fn.call(this, error, null); + fn.call( + this, + new Error(resError.code + ': ' + sysErrors[resError.code]), + null + ); return; } // asserts - for (i = 0; i < this._asserts.length && !error; i += 1) { - error = this._assertFunction(this._asserts[i], res); + for (i = 0; i < this._asserts.length; i += 1) { + // handle promises. + if (typeof this._asserts[i].then === 'function') { + this._asserts[i] + .then(res) + .catch(function(promiseError) { + return fn.call(this, promiseError, res); + }) + .then(function(maybeError) { + if (maybeError instanceof Error) { + return fn.call(this, maybeError, res); + } + }); + return; + } + + // handle functions + maybePromise = this._assertFunction(this._asserts[i], res); + if (maybePromise && typeof maybePromise.then === 'function') { + // function returned a promise + maybePromise + .then(function(maybeError) { // eslint-disable-line no-loop-func + if (maybeError instanceof Error) { + return fn.call(this, maybeError, res); + } + }) + .catch(function(promiseError) { + return fn.call(this, promiseError, res); + }); + return; + } else if (maybePromise instanceof Error) { + // function returned a non-promise. if it is an error, report it. + return fn.call(this, maybePromise, res); + } } // set unexpected superagent error if no other error has occurred. - if (!error && resError instanceof Error && (!res || resError.status !== res.status)) { - error = resError; + if (resError instanceof Error && (!res || resError.status !== res.status)) { + return fn.call(this, resError, res); } - fn.call(this, error || null, res); + // no error + fn.call(this, null, res); }; /** @@ -282,7 +318,17 @@ Test.prototype._assertFunction = function(check, res) { } catch (e) { err = e; } - if (err instanceof Error) return err; + + // We got an error, return it. + if (err instanceof Error) { + return err; + } + + // We got a promise, return it and let the caller figure out if it contains + // an error. + if (err && typeof err.then === 'function') { + return err; + } }; /** diff --git a/package.json b/package.json index 20984045..a5c48dc9 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,7 @@ "methods": "~1.1.2" }, "devDependencies": { + "bluebird": "^3.4.7", "body-parser": "~1.16.0", "cookie-parser": "~1.4.1", "eslint": "^3.14.1", @@ -21,6 +22,7 @@ "eslint-plugin-react": "6.4.1", "express": "~4.14.0", "mocha": "~3.2.0", + "native-or-bluebird": "^1.2.0", "should": "~11.2.0" }, "engines": { diff --git a/test/supertest.js b/test/supertest.js index c9a4927b..a61509c2 100644 --- a/test/supertest.js +++ b/test/supertest.js @@ -6,6 +6,7 @@ var should = require('should'); var express = require('express'); var bodyParser = require('body-parser'); var cookieParser = require('cookie-parser'); +var Promise = require('native-or-bluebird'); process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0'; @@ -342,6 +343,100 @@ describe('request(app)', function() { }); }); + describe('.expect(fn)', function() { + it('should handle a function', function() { + var myFunction = function(res) { + res.text.should.equal('not hey'); + }; + var app = express(); + + app.get('/', function(req, res) { + res.send('hey'); + }); + + return request(app) + .get('/') + .expect(200) + .expect(myFunction) + .then(function(res) { + throw new Error('Test passed.'); + }) + .catch(function(err) { + err.message.should.equal('expected \'hey\' to be \'not hey\''); + }); + }); + + it('should handle function returning a promise', function() { + var myFunction = function(res) { + return new Promise(function(resolve, reject) { + reject(new Error('rejected')); + }); + }; + var app = express(); + + app.get('/', function(req, res) { + res.send('hey'); + }); + + return request(app) + .get('/') + .expect(200) + .expect(myFunction) + .then(function(res) { + throw new Error('Test passed.'); + }) + .catch(function(err) { + err.message.should.equal('rejected'); + }); + }); + + it('should handle promises with callback resolution', function(done) { + Promise.resolve(function(res) { + res.text.should.equal('hey'); + throw new Error('Promise threw.'); + }).then(function(myPromise) { + var app = express(); + + app.get('/', function(req, res) { + res.send('hey'); + }); + + request(app) + .get('/') + .expect(200) + .expect(myPromise) + .end(function(err, res) { + err.message.should.equal('Promise threw.'); + done(); + }); + }); + }); + + it('should handle promises with promise resolution', function() { + return Promise.resolve(function(res) { + res.text.should.equal('hey'); + throw new Error('Promise threw.'); + }).then(function(myPromise) { + var app = express(); + + app.get('/', function(req, res) { + res.send('hey'); + }); + + return request(app) + .get('/') + .expect(200) + .expect(myPromise) + .then(function(res) { + throw new Error('Test passed.'); + }) + .catch(function(err) { + err.message.should.equal('Promise threw.'); + }); + }); + }); + }); + describe('.expect(status[, fn])', function() { it('should assert the response status', function(done) { var app = express();