Skip to content

Commit

Permalink
Add support for promises / async functions in expect. Fixes #401.
Browse files Browse the repository at this point in the history
  • Loading branch information
LegNeato committed Feb 2, 2017
1 parent 199506d commit 77aaded
Show file tree
Hide file tree
Showing 3 changed files with 207 additions and 11 deletions.
75 changes: 64 additions & 11 deletions lib/test.js
Expand Up @@ -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;
}
Expand Down Expand Up @@ -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
Expand All @@ -161,22 +161,65 @@ 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) {
// promise resolved to an error
return fn.call(this, maybeError, res);
}
// promise resolved to a non-error
return fn.call(this, null, 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) {
// promise resolved to an error
return fn.call(this, maybeError, res);
}
// promise resolved to a non-error
return fn.call(this, null, res);
})
.catch(function(promiseError) {
// error resolving the promise.
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);
};

/**
Expand Down Expand Up @@ -282,7 +325,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;
}
};

/**
Expand Down
2 changes: 2 additions & 0 deletions package.json
Expand Up @@ -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",
Expand All @@ -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": {
Expand Down
141 changes: 141 additions & 0 deletions test/supertest.js
Expand Up @@ -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';

Expand Down Expand Up @@ -342,6 +343,146 @@ describe('request(app)', function() {
});
});

describe('.expect(fn)', function() {
it('should handle a function failing', 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 a function passing', 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) {
res.text.should.equal('hey');
})
.catch(function(err) {
(err === null).should.be.true;
});
});

it('should handle function returning a failing 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 function returning a passing promise', function() {
var myFunction = function(res) {
return new Promise(function(resolve, reject) {
resolve(null);
});
};
var app = express();

app.get('/', function(req, res) {
res.send('hey');
});

return request(app)
.get('/')
.expect(200)
.expect(myFunction)
.then(function(res) {
res.text.should.equal('hey');
})
.catch(function(err) {
(err === null).should.be.true;
});
});

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();
Expand Down

0 comments on commit 77aaded

Please sign in to comment.