Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for promises / async functions in expect. Fixes #401. #402

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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