I'm writting tests for an expressjs token-based app, however I read the chai doc and find only cookie based auth. With an workaround the test passed with the code above, however I need the token to be used in others it statements. Any tips are welcome. I'm using mochajs to run tests.
var chai = require('chai');
var chaiHttp = require('chai-http');
var mongoose = require('mongoose');
var server = require('../app');
var Dish = require('../models/dishes');
var should = chai.should();
chai.use(chaiHttp);
describe('Users', function() {
it('should return success on /login POST', function(done) {
var agent = chai.request.agent(server)
agent
.post('/users/login')
.send({"username": "admin", "password": "password"})
.then(function(res) {
res.should.have.cookie('token');
return agent.get('/users')
.then(function(res) {
res.should.have.status(200);
});
});
done();
});
it('should return success on /users GET', function(done) {
chai.request(server)
.get('/users')
.end(function(err, res) {
res.should.have.status(200);
done();
});
});
});
describe('Dishes', function() {
it('should list ALL dishes on /dishes GET', function(done) {
chai.request(server)
.get('/dishes')
.end(function(err, res) {
res.should.have.status(200);
done();
});
});
it('should list a SINGLE dish on /dish/<id> GET');
it('should add a SINGLE dish on /dishes POST');
it('should update a SINGLE dish on /dish/<id> PUT');
it('should delete a SINGLE dish on /dish/<id> DELETE');
});
You can start your test by before, and then get you token
let token = '';
describe('Users', () => {
before((done) => {
const userCredentials = {
username: 'admin',
password: 'password'
};
chai.request(server)
.post('/api/v1/auth')
.send(userCredentials)
.end((err, res) => {
console.log('res', res.body.data.customer.token);
token = `Bearer ${res.body.data.customer.token}`;
done();
});
})
// Your test
})
Related
I'm trying to learn testing with jest and supertest. I have this simple API:
app.get("/", (req, res) => {
res.json({ name: "bob" });
});
app.delete("/delete", async (req, res) => {
const result = await Todos.deleteOne({ _id: req.body.id });
res.send(result);
});
app.post("/add", (req, res) => {
const { item, status } = req.body;
const todo = new Todos({
item,
});
todo.save();
res.send(todo);
});
They all work. I also have these tests **which when individually run they all pass, but when I run them together the delete fails.
My logic is, when adding a post, I'm passing the id of the created post to the test so that the delete test can use
const request = require("supertest");
const app = require("../server");
describe("testing Todos API", () => {
let payload = { id: "6156c95c9ecf28237844489b" };
let item = { item: "test" };
it("get", (done) => {
request(app)
.get("/")
.expect(200)
.end((err, res) => {
expect(res.body).toEqual({ name: "bob" });
expect(res.body.name).toEqual("bob");
done();
});
});
it("post", (done) => {
request(app)
.post("/add")
.send(item)
.expect(200)
.end((err, res) => {
payload.id = res.body._id;
expect(res.body.item).toEqual(item.item);
done();
});
});
it("delete", (done) => {
request(app)
.delete("/delete")
.send(payload)
.end((err, res) => {
console.log(res.body);
expect(res.body.deletedCount).toEqual(1);
done();
});
});
});
But I get the error of error MongoServerError: E11000 duplicate key error collection and deletedCount is 0
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 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();
});
});