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
}
Related
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
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".
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 ?
describe("Company Controller", function() {
var apiUrl;
beforeEach(function(done) {
apiUrl = "http://localhost:3001";
done();
});
it('should register a client without error and return an API key', function(done) {
request({
uri: apiUrl + '/api/v1/company',
method: 'POST',
json: true,
form: {
name: 'My Company'
}
}, function(err, res, body) {
should.not.exist(err);
res.statusCode.should.eql(200);
body.status.should.eql('ok');
should.exist(body.company.api_key);
done();
});
});
it('should generate a new API key for a company', function(done) {
// NEED THE client_id generated in the previous test
});
after(function(done) {
Company.remove().exec();
done();
});
});
How do I get the client_id in the next test?
Generally speaking, making tests with side effects is a brittle practice. Do this often enough, you'll start to encounter some very difficult-to-debug errors where your test suite fails even though every test runs in isolation, and the error messages won't be any help. Ideally every test should "leave the campground" in the same state that it found it.
If you're really insistent on doing this, you could of course set a global variable. Some other options include:
Merging the two tests. Yes, this violates a principle of Single-Assertion-Per-Test that some people hold, but I think the Avoid-Side-Effects principle trumps that one.
Put the registration in the beforeEach function. Yes, by doing this you'll be registering multiple clients per test suite run. This is still my preferred approach.
You should use a stub. For example sinonjs. This way you use a fake function to test another. You don't need to use beforeEach and afterEach if you only need to stub the function once you can define it inside the it function.
describe("Company controller", function() {
describe("dependantFunction()", function() {
var stub;
beforeEach(function() {
stub = sinon.stub(module, 'targetFunction');
stub.onCall(0).callsArgWith(1, ...some valid results...);
stub.onCall(1).callsArgWith(1, ...some expected error...);
stub.throws();
});
afterEach(function() {
stub.restore();
});
it("should do something", function() {
var result;
module.targetfunction(null, result);
dependantfunction(result);
....your tests here....
});
it("should do something else", function() {
...
});
});
});
I am having trouble unit testing with Express on a number of fronts, seems to be a lack of documentation and general info online about it.
So far I have found out I can test my routes with a library called supertest (https://github.com/visionmedia/superagent), but what if I have broken my routes and controllers up, how can I go about testing my controllers independently of their routes.
here is my test:
describe("Products Controller", function() {
it("should add a new product to the mongo database", function(next) {
var ProductController = require('../../controllers/products');
var Product = require('../../models/product.js');
var req = {
params: {
name: 'Coolest Product Ever',
description: 'A very nice product'
}
};
ProductController.create(req, res);
});
});
req is easy enough to mockup. res not so much, I tried grabbing express.response, hoping I could just inject it but this hasn't worked. Is there a way to simulate the res.send object? Or am I going the wrong way about this?
When you are testing your routes, you don't actually use the inbuilt functions. Say for example, ProductController.create(req, res);
What you basically need to do is, run the server on a port and send a request for each url. As you mentioned supergent, you can follow this code.
describe("Products Controller", function() {
it("should add a new product to the mongo database", function(next) {
const request = require('superagent');
request.post('http://localhost/yourURL/products')
.query({ name: 'Coolest Product Ever', description: 'A very nice product' })
.set('Accept', 'application/json')
.end(function(err, res){
if (err || !res.ok) {
alert('Oh no! error');
} else {
alert('yay got ' + JSON.stringify(res.body));
}
});
});
});
You can refer to superagent request examples here.