I'm trying to test an API with Mocha and Supertest without lucky to make it work.
I have the following code:
var supertest = require('supertest');
describe('Routing', function() {
var url = 'http://example.com';
var server = supertest.agent(url);
var credentials = {
user: 'username',
pass: 'password'
};
describe('Login', function() {
it('should login ok given valid credentials', function(done) {
server
.post('/login.php')
.send(credentials)
.end(function(err, res) {
if (err) {
throw err;
}
server.saveCookies(res);
done();
});
});
it('should correctly make an authenticated request', function(done){
server
.get('/api/me/accounts?_=1449865354112')
.end(function(err,res) {
if (err) {
throw err;
}
res.status.should.be.equal(200);
done();
});
});
});
});
The login request works fine, I get authenticated. The second call throws a 401 status.
I read the documentation but I can't make it work.
What is wrong?
thanks!
UPDATE:
I finally get authenticated by sending the params using .field('user', 'myUsername') and .field('pass', 'myPassword').
Also I have to persist the cookie between calls:
cookie = res.headers['set-cookie']; when I get authenticated, and .set('cookie', cookie) in the next requests.
.send() is for your data. .auth() is for your credentials. Try:
it('should login ok given valid credentials', function(done) {
server
.post('/login.php')
.auth(credentials)
.send({"some": "value"})
.expect(200)
.end(function(err, res) {
if (err) {
done(error);
}
server.saveCookies(res);
done();
});
});
See http://visionmedia.github.io/superagent/docs/test.html for a bit more information on supertest.
That is because the session (cookie) is not persisted between your two tests.
First you should do the two calls inside the same test.
Second i remember that i have used superagent to persist the session between two calls to the same server. But it seems that supertest now expose the agent to persist the session.
var supertest = require('supertest');
var app = express();
var agent = supertest.agent(app);
// then you can persist cookie
agent
.post('/login.php')
.auth(credentials)
...
edit :
here is an example of how i have used superagent for tests :
var request = require('superagent');
var postData= {
email: 'john#test.com',
password: 'test'
};
var user1 = request.agent();
user1.post('http://localhost:3000/user/login')
.send(postData)
.end(function (err, res) {
expect(err).to.not.exist;
expect(res.status).to.equal(200);
var result = res.body;
expect(result.data.message).to.equal('Login successful');
user1.get('http://localhost:3000/user')
.end(function (err, res) {
expect(err).to.not.exist;
expect(res.status).to.equal(200);
var result = res.body;
expect(result.data.email).to.equal('john#test.com');
done();
});
});
Related
I'm trying to test a authenticated route.
This is my code:
let request = require('supertest');
var superagent = require('superagent');
var agent = superagent.agent();
var theAccount = {
name: '*********',
role: 'admin',
id: '115039452833383267752'
};
request = request('http://localhost:3000');
describe('Live-score', () => {
before(function (done) {
request
.post('/api/login')
.send(theAccount)
.end(function (err, res) {
if (err) {
throw err;
}
agent.saveCookies(res);
done();
});
});
it('Should work', (done) => {
agent.attachCookies(req);
request
.get('/api/live-score')
.send(agent)
.set('Accept', 'text/html')
.expect('Content-Type', 'application/json; charset=utf-8')
.expect(200, done);
});
However I get the following error:
TypeError: agent.saveCookies is not a function
I'm using Google Passport strategy.
The one place I saw some similar code to this, the agent was declared within the before block.
You could try:
before(function (done) {
agent = superagent.agent();
request
.post('/api/login')
.send(theAccount)
.end(function (err, res) {
if (err) {
throw err;
}
agent.saveCookies(res);
done();
});
});
Reference: https://github.com/visionmedia/superagent/issues/352
I am a newbie in testing in Javascript and trying to test my NodeJS backend using mocha and chai.
How all of my routes are filled with a middleware which does not allow people to go forward if they aren't logged in.
Something like this
app.post('/user/analytics/driverdata', checkauth, function(req, res) {
analytics.driverData(req, res);
});
where checkauth is
var checkauth = function(req, res, next) {
console.log("In checkauth");
if (req.isAuthenticated()) {
next();
} else {
console.log("Doesn't authenticate");
res.status(401);
res.set('Content-Type', 'application/json');
res.end(JSON.stringify({
'success': false
}));
}
};
The isAuthenticated parameter is attached to request by PassportJS, when it deserializes a request.
What I want to do is write a test for
app.post('/user/analytics/driverdata', checkauth, function(req, res) {
analytics.driverData(req, res);
});
this API. in which I am failing as I am not logged in hence unable to reach there.
So I wrote a beforeEach to login the user beforeEach it. It goes like this.
var expect = require('chai').expect;
var request = require('superagent');
beforeEach(function(done){
//login into the system
request
.post("http:localhost:5223/user/authenticate/login")
.send({username : "saras.arya#gmail.com", password : "saras"})
.end(function assert(err, res){
if(err){
console.log(err);
done();
}
else{
done();
}
});
});
I don't know what am I doing wrong and the internet has failed me. Any help to point out where am I going wrong will be appreciated.
After seeing a lot of stuff and fidgeting around I think I finally cracked it. It would be harsh not to mention the answer here which introduced me to the concept of an agent. Which helped me crack this.
Inside your describe block, or probably before the block you can have the following it.
var superagent = require('superagent');
var agent = superagent.agent();
it('should create a user session successfully', function(done) {
agent
.post('http://localhost:5223/user/authenticate/login')
.send({
username: 'whatever#example.com',
password: 'ssh-its-a-secret'
})
.end(function(err, res) {
console.log(res.statusCode);
if (expect(res.statusCode).to.equal(200))
return done();
else {
return done(new Error("The login is not happening"));
}
});
});
the agent variable holds the cookies for you, which are then used by PassportJS to authenticate you.
here is how you do it. So the agent variable is inside a describe. In the same describe inside another it.
it("should test analytics controller", function(done) {
agent.post('http://localhost:5040/user/analytics/driverData')
.send({
startDate: "",
endDate: "",
driverId: ""
})
.end(function(err, res) {
if(!err)
done();
});
});
This function passes like a charm. This was one complete documentation that was missing.
I am trying to test a route with authentication in my Node / Express / Mongoose back-end.
Here's the test file
var should = require('should');
var _ = require('lodash');
var async = require('async');
var app = require('../../../../app');
var request = require('supertest');
var mongoose = require('mongoose');
var User = mongoose.model('User');
var Firm = mongoose.model('Firm');
var firm, user, userPassword, createdFirm, loggedInUser;
describe('GET /api/firms', function(){
beforeEach(function (done) {
firm = new Firm({
company: 'My test company',
corporateMail: 'test.com'
});
userPassword = 'password';
user = new User({
fistname: 'Fake User',
lastname: 'Fake User',
email: 'test#test.com',
job: 'Partner',
firmName:firm.company,
password:userPassword,
isActivated:true,
_firmId:firm._id
});
function createFirm(cb){
request(app)
.post('/api/firms')
.send(firm)
.expect(201)
.end(function(err, res){
if ( err ) throw err;
createdFirm = res.body;
cb();
});
}
function createUser(cb){
request(app)
.post('/api/common/users')
.send(user)
.expect(200)
.end(function(err, res){
createdUser = res.body;
if ( err ) throw err;
cb();
});
};
async.series([function(cb){
createFirm(cb);
}, function(cb){
createUser(cb);
}], done);
});
afterEach(function (done) {
firm.remove();
user.remove();
done();
});
it('should respond with 401 error', function(done) {
request(app)
.get('/api/firms')
.expect(401)
.end(function(err, res) {
if (err) return done(err);
done();
});
});
it('should login', function(done) {
request(app)
.post('/auth/local')
.send({email:user.email, password:user.password})
.expect(200)
.end(function(err, res) {
if (err) return done(err);
done();
});
});
it('should respond with 200 after login', function(done) {
request(app)
.get('/api/firms')
.expect(200)
.end(function(err, res) {
if (err) return done(err);
done();
});
});
});
In the workflow the firm object is created first and then returns its Id so I can create the user with the firmId as a reference.
I would like to test the /api/firms route after the user is authenticated but in spite of my various attempts (using superagent, logging in the before section) I always get a 401 response in the last should section instead of an expected 200.
Actually the important thing to keep in mind is, as KJ3 said, how the authentication is set up. In my case I forgot to mention that I was using jwt. The way it works is the following, you supply a username and a password and the server returns a token created with jwt.
So it makes sense to send the token back for each request in the tests.
The way to achieve this is first by storing the token after authentication in the before section
function createUser(cb){
request(app)
.post('/api/users')
.send(user)
.expect(200)
.end(function(err, res){
if ( err ) throw err;
authToken = res.body.token;
cb();
});
};
Then by adding .set in the request with the token in the correct format ('Bearer ' + token , which is defined in the authentication service):
it('should respond with 200', function(done) {
var authToken = 'Bearer ' + createdUser.token;
request(app)
.get('/api/firms')
.set('Authorization', authToken)
.expect(200)
.end(function(err, res) {
if (err) return done(err);
done();
});
});
In the case the test sends a 200 back, which is expected and sends a 401 if the .set(...) is commented out.
Good news is that this is achieved with supertest, so no need to add anything, less good news is that you need to add the .set(...) to each test request.
If you were to go through the last 2 tests in a browser, depending on how you have it setup, yes it would work thanks to cookies and sessions, but here the /api/firms test is independent of the auth/local test. So a 401 is the correct response.
It really depends on how your auth is setup, but you need to authenticate on the /api/firms test too. Either by sending the credentials again (every single one of my mocha tests authenticates each time) or implement sessions into the tests, see this SO post for some direction.
I want to write some tests for some routes and I want to do something like this:
var should = require('should');
var app = require('../../app');
var request = require('supertest');
describe('Create and check that the create was successfull', function() {
var user_id = '';
it('should add a new case and return a JSON array', function(done) {
var newUser = {nume : 'Test', prenume: 'test', varsta : 23};
request(app)
.post('/api/new_user')
.send(newUser)
.expect(201)
.expect('Content-Type', /json/)
.end(function(err, res) {
if (err) return done(err);
res.body.should.be.instanceOf(Array);
res.body.should.have.property('_id');
user_id = res.body._id;
done();
});
});
it('should return the new user ', function(done) {
request(app)
.get('/api/new_user/' + user_id)
.expect(200)
.expect('Content-Type', /json/)
.end(function(err, res) {
if (err) return done(err);
res.body.should.be.instanceOf(Object);
res.body._id.should.be.exactly(user_id);
done();
});
});
});
I am not sure if the two it statements are executed one after the other or each one is done async and when I get to the second it the first one is not executed so it will fail because the insert was not done in the server.
Should I use async.series?
In your example supertest is only responsible for the chain from request(app) down, so it's actually the provider of the describe() and it() calls that determines the order, or lack thereof, in which your tests are executed, which I guess is mocha, right?
If so, Mocha will run your testcases in order (as in, the second one will be called once the first one has finished).
Supertest request can't persist session therefore your second test case seems failing. Basically the second case running as it is not aware at all about the first case.
You can persist your session with request.agent.
Below is a quick example:
var should = require('should');
var app = require('../../app');
var request = require('supertest');
describe('Create and check that the create was successfull', function() {
var session;
var new_user = {name:'Test',presume:'test',vast:'23'};
var user_id;
before (function(done){
session = request.agent(app);
session
.post('/api/new_user')
.send('new_user')
.expect(201)
.expect('Content-Type', /json/)
.end(function(err, res) {
if (err) return done(err);
res.body.should.be.instanceOf(Array);
res.body.should.have.property('_id');
user_id = res.body._id;
done();
});
});
it('should return the new user ', function(done){
session
.get('/api/new_user/' + user_id)
.expect(200)
.expect('Content-Type', /json/)
.end(function(err, res) {
if (err) return done(err);
res.body.should.be.instanceOf(Object);
res.body._id.should.be.exactly(user_id);
done();
});
});
...
});
From all the documents and examples I've read, it should be possible to persist a session in supertest using an agent:
var app = require('../../../server'),
should = require('should'),
request = require('supertest'),
mongoose = require('mongoose'),
User = mongoose.model('User'),
_ = require('lodash');
var user = {
name: 'Sterling Archer',
email: 'duchess#isis.com',
password: 'guest'
};
describe('user.me', function() {
var url = '/user';
var agent = request.agent(app);
var new_user = new User(user);
new_user.save();
it('should return a user object', function(done) {
agent
.post('/signin')
.send(_.omit(user, 'name'))
.expect(200).end(function(err, res) {
console.log(res.headers['set-cookie']);
});
agent
.get(url)
.expect(200)
.end(function(err, res) {
should.not.exist(err);
console.log(res.headers['set-cookie']);
res.body.should.have.property('user');
res.body.user.should.have.properties('name', 'email');
done();
});
});
});
The session should persist since each request above is using the same agent. However that doesn't seem to be the case - the output from the set-cookie logs follows:
[ 'connect.sid=s%3AsFl1DQ4oOxC8MNAm79mnnr9q.gMkp8iEWtG8XlZZ2rkmheBwxKAyLyhixqDUOkYftwzA; Path=/; HttpOnly' ]
[ 'connect.sid=s%3AEzfbPyRGMff7yBXc9OAX3vGT.Ze2YoxZzuB6F6OwOk7mvrk96yPP2G4MGV%2Bt1rVjTyS8; Path=/; HttpOnly' ]
passport.js is being used for authentication and sessions. I would expect connect.sid above to be constant for both requests, but it looks like a new session is being created on each call so the agent isn't logged in on the second call and no user object is returned.
When I test my app manually in a browser connect.sid remains constant after login and the functionality I'm testing does work.
I must be doing something wrong with agent, and I'm hoping someone can spot it. Otherwise, suggestions on how I could debug the issue would be much appreciated.
You're sending the second request without waiting for the first one to be responded; if you don't give the agent time to receive the Set-Cookie header in the response and use its value as a the Cookie header in the same request, a new session will be created. Try it this way:
it('should return a user object', function(done) {
agent
.post('/signin')
.send(_.omit(user, 'name'))
.expect(200).end(function(err, res) {
console.log(res.headers['set-cookie']);
agent
.get(url)
.expect(200)
.end(function(err, res) {
should.not.exist(err);
console.log(res.headers['set-cookie']); // Should print nothing.
res.body.should.have.property('user');
res.body.user.should.have.properties('name', 'email');
done();
});
});
});
Esteban's suggestion pointed out that I was overlooking the asynchronous nature of the code. Going back to this example I realized I missed the significance of logging in in a separate test; doing so solved my problem.
Though I'm now creating dependent tests, which I'm not crazy about.
var app = require('../../../server'),
should = require('should'),
request = require('supertest'),
mongoose = require('mongoose'),
User = mongoose.model('User'),
_ = require('lodash');
var user = {
name: 'Sterling Archer',
email: 'duchess#isis.com',
password: 'guest'
};
var agent = request.agent(app);
describe('User Controller', function() {
before(function(done) {
var new_user = new User(user);
new_user.save();
done();
});
describe('user.signin', function() {
var url = '/signin';
it('should signin and return a user object', function(done) {
agent
.post(url)
.send(_.omit(user, 'name'))
.expect(200)
.end(function(err, res) {
should.not.exist(err);
res.body.should.have.property('user');
res.body.user.should.have.properties('name', 'email');
done();
});
});
});
describe('user.me', function() {
var url = '/user';
it('should return a user object', function(done) {
agent
.get(url)
.expect(200)
.end(function(err, res) {
should.not.exist(err);
res.body.should.have.property('user');
res.body.user.should.have.properties('name', 'email');
done();
});
});
});
after(function(done) {
User.remove().exec();
done();
});
});