Why does my timeout is exceeded? - node.js

I'm developing an API with Node.js and Express and i'm using Mocha and Supertest to write unit tests. I have a BIG file of tests which test every route with almost random parameters to see if my error handling works well.
Everything was great until, for no reason, my requests are starting to timeout.
This is more or less my code :
var supertest = require("supertest");
var should = require("should");
var server = supertest.agent("http://localhost:3000");
function requestAuth(url, type, auth, params, callback) {
if (params == null) {
server[type](url)
.type('form')
.auth(auth.email, auth.password)
.expect("Content-type",/json/)
.expect(200)
.end(callback);
}
else {
server[type](url)
.send(params)
.auth(auth.email, auth.password)
.type('form')
.expect("Content-type",/json/)
.expect(200)
.end(callback);
}
}
describe('Testing route 1', function() {
describe('Testing param 1 error handling', function() {
it('should return error 1', function(done) {
requestAuth(route1, "post", {email: email, password: password}, {param1: "blahblahblah"},
function(err, res) {
res.body.should.have.property('error');
done();
});
});
it('should return error 2', function(done) {
requestAuth(route1, "post", {email: email, password: password}, {param1: "blahblahblah"},
function(err, res) {
res.body.should.have.property('error');
done();
});
});
// etc
});
describe('Testing param 2 error handling', function() {
it('should return error 3', function(done) {
requestAuth(route1, "post", {email: email, password: password}, {param1: "blahblahblah"},
function(err, res) {
res.body.should.have.property('error');
done();
});
});
it('should return error 4', function(done) {
requestAuth(route1, "post", {email: email, password: password}, {param1: "blahblahblah"},
function(err, res) {
res.body.should.have.property('error');
done();
});
});
// etc
});
//etc
});
describe('Testing route 2', function() {
//etc
});
Except that i have of LOT of tests.
At some point, let say that when i'm testing route 8, every tests starts to fail with the following message :
12) Route 8 Testing Param 1 error handling should return error 1:
Error: timeout of 2000ms exceeded
at null.<anonymous> (/usr/lib/nodejs/mocha/lib/runnable.js:139:19)
at Timer.listOnTimeout (timers.js:92:15)
I really don't get it. Everything worked well since then, done is called at the end of every request, should be good. Nothing happens on the server side even though routes are ok. This is really weird...
Also, if the route 8 tests are getting weird, and that i comment route 7 tests for example, than route 9 tests will starts to act wrong.
I think this is coming from supertest. Is it possible that it is overloaded ? How could i fix that ?
Thanks in advance for your responses.

Your tests themselves have a time limit to complete your actions. This means that if the resource isn't set up and available in those two seconds, or if the test completes after two seconds it will fail. Use this.timeout = [milliseconds] as the first line of the failing test to extend the timeout.
MochaJS Test Level Timeouts

Related

Dynamically testing authentication RESTful API (NodeJS, Mocha, SuperAgent)

Goal:
I am trying to test my authentication RESTful API. The tools i am using are NodeJS with the modules: Mocha, Supertest(.agent) and chai(.assert).
What i tried:
var users = [ new User("admin", "secretPass"), new User("guest", "Pass")];
describe('request some protected data', function(){
users.forEach(function(user){
before(function(){
agent.post('/login').send({ user.username, user.key })
.end(function(err, res) {
}
}
it('get data', function(done){
agent.get('/data').send({ user.username, user.key })
.end(function(err, res) {
// assertions on data
}
}
}
}
The problem:
Using something similar to the snippet above results in multiple before functions being executed at once and after that all the tests(it).
So agent will always be logged in as the last user in users.
I tried to replace before with beforeEach and place it above users.forEach, but then user will be out of scope.
Can anyone provide me with a small snippet of code that will explain a suitable solution for my problem?
You need to tell Mocha that your before function is asynchronous, by accepting a done parameter and calling it when logging in has completed, e.g.
before(function(done){
agent.post('/login').send({ user.username, user.key })
.end(function(err, res) {
// I assume you're logged in here...
done();
}
}
Of course, you'll also want to add the required error handling etc. You'll probably want to create a new describe block for each user too, otherwise all of the login calls will run before any user tests happen, e.g.
users.forEach(function(user){
describe("user X tests", function() { // <-- NEW
before(function(){
agent.post('/login').send({ user.username, user.key })
.end(function(err, res) {
}
}
it('get data', function(done){
agent.get('/data').send({ user.username, user.key })
.end(function(err, res) {
// assertions on data
}
}
}); // <- NEW
}

Getting random results on Travis

My tests gives random results on Travis. Here are the logs where my tests did fail on Travis:
https://travis-ci.org/superzaky/node-portfolio-zaky/builds/149718369
And here are the logs where my tests did pass on Travis:
https://travis-ci.org/superzaky/node-portfolio-zaky/builds/149560005
Both branches contain the exact same code.
And on my computer I can also make the tests pass link to screenshot: http://i.imgur.com/zXOINsD.png
I had also cloned my repository on my laptop. And there the tests also pass.. I suspect it has something to do with the way I had written
my tests in 100-Login.js:
require('../utils');
require('events').EventEmitter.prototype._maxListeners = 100;
var supertest = require("supertest");
var should = require("should");
var assert = require('chai').assert;
var app = require('../../app');
// This agent refers to PORT where our program is running.
var server = supertest.agent(app);
// UNIT test begin
describe("A user logs in", function () {
it('should create a SINGLE session on /api/auth/login POST', function (done) {
//calling LOGIN api
server
.post('/api/auth/login')
.send({
username: "jimmy",
password: "open"
})
.expect("Content-type", /json/)
.expect(200)
.end(function (err, res) {
var data = {
_id: "000000000000000000000001",
name: "Jimmy Doe",
username: "jimmy",
admin: false
};
res.status.should.equal(200);
assert.deepEqual(res.body, data);
done();
});
});
it('should display a SINGLE session on /api/auth/ GET', function (done) {
//We check if a session is created by sending a GET request to /api/auth
server
.get('/api/auth/')
.expect("Content-type", /json/)
.expect(200)
.end(function (err, res) {
var data = {
_id: "000000000000000000000001",
name: "Jimmy Doe",
username: "jimmy",
admin: false
};
res.status.should.equal(200);
assert.deepEqual(res.body, data);
done();
});
});
it('should delete a SINGLE session on /api/auth/logout GET', function (done) {
//We check if a session is created by sending a GET request to /api/auth
server
.get('/api/auth/logout')
.expect("Content-type", /json/)
.expect(200)
.end(function (err, res) {
var data = "Successfully logged out";
res.status.should.equal(200);
assert.deepEqual(res.body, data);
done();
});
});
it('should NOT display a SINGLE session on /api/auth/ GET', function (done) {
//We check if a session is created by sending a GET request to /api/auth
server
.get('/api/auth/')
.expect("Content-type", /json/)
.expect(404)
.end(function (err, res) {
var data = "Session not found";
res.status.should.equal(404);
assert.deepEqual(res.body, data);
done();
});
});
});
require('events').EventEmitter.prototype._maxListeners = 0;
And here is the actual code that made the tests pass link: https://github.com/superzaky/node-portfolio-zaky/blob/travis/controllers/AuthController.js
But that looks fine to me. Anyone that maybe has a clue on how to fix this problem?
You should check what's in your local database.
Based on your other tests, which initially register a user with username = john (see this test, line 20), there will be no user with username = jimmy, which is what you use as the username credential when POSTing to /api/auth/login in the test that fails (see this test, line 18).
The tests that pass on your local machine probably pass because you also have a user named "jimmy" in your local db.
As far as why the second test fails, it fails because the session creation test fails right before it and it is expecting a session to exist for "jimmy".

How do I get the actual server error when running supertest in mocha?

I have this code using supertest and mocha:
import request from 'supertest';
//....
var newGame;
describe('Creating game', function() {
beforeEach(function(done) {
request(app)
.post('/api/games')
.send({
owner: 'Mr. X',
})
.expect(201)
.expect('Content-Type', /json/)
.end((err, res) => {
if (err) {
return done(err);
}
newGame = res.body;
done();
});
});
describe('the created game', function() {
it('should name the specified owner', function() {
newGame.owner.should.equal('Mr. X');
});
...
})
});
When the server code throws some exception (e.g. accessing properties of an undefined object) I get this stack trace
Error: expected 201 "Created", got 500 "Internal Server Error"
at Test._assertStatus (D:\Codes\theApp\node_modules\supertest\lib\test.js:232:12)
at Test._assertFunction (D:\Codes\theApp\node_modules\supertest\lib\test.js:247:11)
at Test.assert (D:\Codes\theApp\node_modules\supertest\lib\test.js:148:18)
at Server.assert (D:\Codes\theApp\node_modules\supertest\lib\test.js:127:12)
at emitCloseNT (net.js:1521:8)
instead of the actual error that says something like "accessing properties of undefined". How can I get the actual error?
There are probably a lot of ways to tackle this, but I don't believe mocha or supertest have access to the actual error that caused the 500 to occur.
What are you using to create app? If it's Express, for example, error-handling middleware can be added during testing which causes any 500-inducing errors to be logged to the console.
like this for example:
function logErrors (err, req, res, next) {
console.error(err.stack)
next(err)
}
const request = supertest(app.use(logErrors))
You can have your test code listen for the uncaughtException event that the process will raise. As long as the express app does all of its processing in the same process as the test harness any unhandled exception in the process will be send to the process uncaughtException event handler. Now, here's where it can get a bit tricky. Since, it's event based it can be fired at any time there is an unhandled exception. So, if you want to be more explicit and you only want to handle exceptions from the system under test, you will need to add/remove the listener before/after the system under test code is ran. Here's your example updated to listen for an unhandled exception.
import request from 'supertest';
//....
var newGame;
describe('Creating game', function() {
beforeEach(function(done) {
var unhandledException = undefined;
var unhandledExceptionCallback = function(err) {
unhandledException = err;
}
process.on('uncaughtException', unhandledExceptionCallback);
request(app)
.post('/api/games')
.send({
owner: 'Mr. X',
})
.expect(201)
.expect('Content-Type', /json/)
.end((err, res) => {
process.removeListener('uncaughtException', unhandledExceptionCallback);
if (unhandledException !== undefined){
return done(unhandledException);
} else if (err) {
return done(err);
}
newGame = res.body;
done();
});
});
describe('the created game', function() {
it('should name the specified owner', function() {
newGame.owner.should.equal('Mr. X');
});
...
})
});
I think this is an api design issue. An api should provide a client with useful error information, especially if differences in client input are causing the server side error.
Your can do this in your endpoint or globally in some middleware.

send authenticated request via supertest with mocha on a passport managed API

It's been 24 hours since I first had this issue. I know there plenty of similar ones, specially on SO but I can't figure out how to solve it.
I have specific requirement :
I have some routes to test without authentication.
I have some routes to test with specific users.
I use a passport local strategy.
So I decided to test my API like :
var request = require('supertest');
...
describe('Routes', function () {
var anAgent;
var anotherAgent;
before(function (done) {
anAgent = request.agent(app);
anotherAgent = request.agent(app);
async.parallel([
function (callback) {
anAgent
.post('/api/users/login')
.send({user:user, password:password})
.expect(200)
.end(callback);
},
function (callback) {
userAgent
.post('/api/users/login')
.send({user:anotherUser, password:anotherPassword})
.expect(200)
.end(callback);
}
], done);
});
describe('/superroute', function () {
it('should return the user', function (done) {
anAgent
.post('/api/superroute')
.send(params)
.expect(200)
.end(function (err, res) {
should.not.exist(err);
res.body.err.should.equal('Super route');
done();
});
});
...
});
...
});
The route /superroute is describe like
express.Router().post('/superroute', auth.ensureAuthenticated, superCtrl.superroute);
Where ensureAuthenticated middleware call the req.isAuthenticated() of passport.
This API works fine when I use it from a simple angular front but when I run the test with mocha, the passport.deserializeUser method is not called and isAuthenticated return false. With the fact that the user is correctly logged and retrieved from the /login call, it's all I know.
So the call returns 401 instead of 200.
What can I've possibly missed ?

Node.js / Mocha / Supertest REST api -- a del() stopped working after I added a second suite

so I've got a model I'm trying to test. It has custom endpoints as well as the standard REST endpoints. I built a suite to test the standard ops. First I posted, then I upserted(a put w/o an id...basically a post), then I got. Finally I deleted the entry created when I posted, and just clean up after myself I put in an after hook to delete the point I had upserted. This worked and it was good.
Then, in the same file I added another suite, this one testing my custom endpoints. Now my hook that was supposed to delete the upserted point failed. This was not good. I tried wrapping my test for delete in a new describe statement, but again it didn't work. Eventually I did away w/ the hook altogether and just sent 2 .del calls and it still didn't work. I'm at my wits end. Please let me know if you can figure out why this isn't working and how to make it work. Thank you in advance!
Here is my code:
var request = require('supertest');
var app = require('../server');
var chai = require('chai');
var chance = require('chance').Chance();
var assert= require('assert');
function json(verb, url) {
return request(app)[verb](url)
.set('Content-Type', 'application/json')
.set('Accept', 'application/json')
.expect('Content-Type', /json/);
}
describe('Testing standard REST endpoints-(CREATE, GET,PUT, DELETE)', function() {
//This suite tests the CREATE functionality
var ptOneID;
var lat1 =chance.latitude({fixed: 7, min: 41, max: 42});
var lng1 = chance.longitude({fixed:7, min: -88, max:-87});
it('should post a new point', function(done) {
var pt1 = new app.loopback.GeoPoint({lat: lat1, lng: lng1});
json('post', '/api/Points')
.send({
location: pt1,
})
.expect(200)
.end(function(err, res) {
ptOneID= res.body.id;
done();
});
});
//This test preforms a put with out an id-- an upsert
var lat5 =chance.latitude({fixed: 7, min: 41, max: 42});
var lng5 = chance.longitude({fixed:7, min: -88, max:-87});
var ptTwoID
it('should preform an UPSERT since were not specifying ID', function(done) {
var pt5 = new app.loopback.GeoPoint({lat: lat5, lng: lng5});
json( 'put','/api/Points/' )
.send({
location: pt5,
})
.end(function(err, res) {
var updatedPoint = res.body;
ptTwoID = updatedPoint.id;
done();
})
});
it('should return a list of all points', function(done) {
json('get', '/api/Points')
.expect(200)
.end(function(err, res) {
var points = res.body;
assert(Array.isArray(points));
assert.equal(points.length, 2, 'array length incorrect');
done();
});
});
//originally I didnt have the delete tests wrapped in a describe I added it to see if it changed anything
describe('DELETE has its own suite', function() {
//Delete the point
it('should DELETE the Point created by our POST test', function(done){
json('del', '/api/Points/' + ptOneID)
.end(function(err, res) {
assert.equal(res.status, 204, 'delete failed');
done();
})
});
it('should DELETE the Point we upserted',function(done){
json('del', '/api/Points'+ ptTwoID)
.end(function(err, res) {
assert.equal(res.status, 204, 'upsert delete failed');
done();
});
});
it('testing to see if posted point is still there', function(done){
json('get', '/api/Points/' + ptOneID)
.end(function(err, res) {
assert.equal(res.status, 404, 'posted point not deleted unsure emoticon');
done();
})
});
//this part was originally wrapped in an after hook, and worked perfectly until I added the describe for custom endpoints
it('testing to see if upserted point is still there', function(done){
json('get', '/api/Points/' + ptTwoID)
.end(function(err, res) {
assert.equal(res.status, 404, 'upserted pt not deleted unsure emoticon');
done();
})
});
});
});
//This suite tests the beforeSave behavior
describe('Testing Custom endpt', function() {
blah blah blh
});
One last interesting thing: When I try to do the upsert delete, the delete returns a 404, meaning not found, however the get returns a 200 status meaning it was successfully found. Please please let me know what is going on here.

Resources