I try to test this express route with mocha, supertest and sinon. Test can't pass promise, it stop after the first mongoose call in User.find callback function with a pending error message :
Error: timeout of 2000ms exceeded. Ensure the done() callback is being called in this test.
I call done() in the callback but nothing...
module.exports = function(app, User) {
app.route('/appointments')
.post(function(req,res){
User.find({'department': req.body.department}, function(err, workers){
return workers._id;
}).then(function(allWorkers){
var deferred = Q.defer();
function sortWorkers(worker){
return Appointments.find({worker: worker._id, start: req.body.date});
};
Q.all(_.map(allWorkers, sortWorkers)).done(function (val) {
deferred.resolve(val);
});
return deferred.promise;
}).then(function(workers){
console.log(workers);
})
.catch(function(error) {
console.log(error);
})
.done();
})
};
This is my begin test :
it("should save a user and not save the same", function(done){
var appointments = new Appointments({title: 'okok',worker: '580359c86f7159e767db16a9',start:'2015-04-08T02:50:04.252Z' ,department: 95});
console.log('appointments',appointments);
request(app)
.post("/appointments")
.send(appointments)
.expect(200)
.end(function(err,res){
console.log('ok',res);
done();
});
});
First, you don't need to call done at User.find promise
Also, your app.route('/appointments').post never returns anything as a response, try adding
res.end();
where you have console.log on the .then and .catch of your promise. You can use HTTP status code as well, like
...
}).then(function(workers){
res.status(200).end();
})
.catch(function(error) {
res.status(500).end();
})
This will ensure that .end(function(err,res){ ... }) is called on your test and the correct done function is called.
Your should always return promise from your unit test.
All you need is to add return before the request(app):
return request(app)
.post("/appointments")
. . .
.then(() => {
expect(<your actual value>).to.equal(<expected value>)
})
I found the solution :
In my some of .then function have no condition and return nothing if workers array for example if empty that's why my test return timeout of 2000ms exceeded.
I add :
User.find({'department': req.body.department}, function(err, workers){
if(workers.length == 0){
res.status(500).json({ message: "Nobody in this department" });
}
return workers;
})...
Related
I have a function:
const createUser = (name, email) => {
const namePromise = db.findOne({name}).exec();
const emailPromise = db.findOne({email}).exec();
return Promise.all([namePromise, emailPromise])
.then(([foundName, foundEmail]) => {
if (foundName || foundEmail) {
return Promise.reject(new Error("Already exists."));
}
// In the real implementation, a new row is added to db and returns a Promise.
return Promise.resolve({uuid: "uuid1"});
})
}
And I have a mocha test:
describe('Test', function() {
it('succeed', function() {
return createUser("user1", "name1")
.then(function(result) {
// should print '{uuid: "uuid1"}'
console.log(result);
expect(result.uuid).to.equal("uuid1");
})
.catch(function(err) {
expect(err).to.equal(null);
});
});
})
npm test result
Error: Timeout of 2000ms exceeded. For async tests and hooks, ensure "done()" is called; if returning a Promise, ensure it resolves. (/Users/Federer-HYJ/Documents/coding/NodeJS/projects/chatroom_nodejs/backend_v2/test/models_user_test.js)
{ uuid: "uuid1" }
The correct result { uuid: "uuid1" } is printed. But timeout comes before that.
Theoretical, the returned promise from createUser will be captured in it(). Then the test will pass. But it seems it() terminates before createUser() has been resolved.
What's wrong here???
I'm using sinon to stub a function res.status, res.status is called in both unit test below, one pass and the other fail, I even debugged and went line by line and see function is being called yet sinon fails and say the function wasn't called.
this is controller that I'm testing (ActivityController.js)
exports.findActivity = (req, res) => {
ActivityService.findActivity(req.params.id, req.user).then(result => {
res.status(200).json({result: result})
}).catch(err => {
res.status(500).json({msg: err.message})
})
}
and here are the unit tests, the first test pass fine yet the second test doesn't
it('should return Activity obj with status 200', async () => {
expectedResult = activity
sinon.stub(ActivityService, 'findActivity').resolves(expectedResult)
await ActivityController.findActivity(req, res)
sinon.assert.calledWith(ActivityService.findActivity)
expect(res.status.calledOnce).to.equal(true)
sinon.assert.calledWith(res.status, 200)
})
it('should return status 500 on server error', async () => {
sinon.stub(ActivityService, 'findActivity').rejects()
await ActivityController.findActivity(req, res)
sinon.assert.calledWith(ActivityService.findActivity, req.params.id, req.user)
expect(res.status.calledOnce).to.equal(true)
sinon.assert.calledWith(res.status, 500)
})
I debuged the second unit test and see ActivityController go through catch and call res.status(500) yet the unit test fails in res.status.calledOnce
Are you reseting the res.status stub in between each test? res.calledOnce might not be true since it might be called more than once.
It seems that expect(res.status.calledOnce).to.equal(true) was called immediately before waiting for sinon.assert.calledWith.
putting await before sinon.assert fixed the problem.
so the correct code is
it('should return status 500 on server error', async () => {
sinon.stub(ActivityService, 'findActivity').rejects()
await ActivityController.findActivity(req, res)
await sinon.assert.calledWith(ActivityService.findActivity, req.params.id, req.user)
expect(res.status.calledOnce).to.equal(true)
sinon.assert.calledWith(res.status, 500)
})
I have a restify action code block below:
function retriveAll(req, res, next) {
db.user
.find({where: {id: 1})
.then(function(user){
res.send(user);
})
.catch(function(details){
res.send(details.message);
})
.finally(function(){
next();
});
}
I want to test this action specifically validating that res.send() was called within this code block. And later on validating the res.send() returned data. I'm using SinonJs and Mocha for testing framework. Here's a sample test code block for the method above.
describe('retrieveAll()', function() {
reqStub = {};
resStub = {send: sinon.stub()};
nextStub = sinon.stub();
beforeEach(function() {
module.retrieveAll(reqStub, resStub, nextStub);
});
// this doesn't work
// and the sub.calledCount is 0
// i wonder if it's because the res.send() is inside a Promise code block???
// if I move the res.send() out from Promise, just before next(), then it works
it('should call res.send()', function() {
sinon.assert.calledOnce(resStub.send);
});
// this one works
it('should call next', function() {
sinon.assert.calledOnce(nextStub);
});
});
Could someone shed some light?
The beforeEach()'s callback function receives a done parameter that can be called to signal an asynchronous completion. Since your retriveAll function calls the last parameter (next) as the last action, you can pass that parameter as the next value and it should work:
beforeEach(function(done) {
module.retrieveAll(reqStub, resStub, done);
});
You will however loose the nextStub, so... alternatively, you could spy on that done function:
describe('retrieveAll()', function() {
var reqStub = {};
var resStub = {send: sinon.stub()};
var nextSpy;
beforeEach(function(done) {
nextSpy = sinon.spy(done);
module.retrieveAll(reqStub, resStub, done);
});
// this doesn't work
// and the sub.calledCount is 0
// i wonder if it's because the res.send() is inside a Promise code block???
// if I move the res.send() out from Promise, just before next(), then it works
it('should call res.send()', function() {
sinon.assert.calledOnce(resStub.send);
});
// this one works
it('should call next', function() {
sinon.assert.calledOnce(nextSpy);
});
});
So, I got this working thanks for #Amit for pointing me to the done callback on beforeEach
First, I modified the retrieveAll so the next callback is included in the promise chain. I put it in the finally handler making sure that next will be called after all process.
Second, I passed the done to beforeEach and then nextStub will spy on done callback.
Third, instead of passing done cb to module.v1.retrieveAll I used nextStub. This solve the issue on testing nextStub.calledOnce.
The updated code now looks:
function retriveAll(req, res, next) {
db.user
.find({where: {id: 1})
.then(function(user){
res.send(user);
})
.catch(function(details){
res.send(details.message);
})
.finally(function(){
next();
});
}
describe('retrieveAll()', function() {
var reqStub = {};
var resStub = {send: sinon.stub()};
var nextStub;
beforeEach(function(done) {
nextStub = sinon.spy(done);
module.retrieveAll(reqStub, resStub, nextStub);
});
// this doesn't work
// and the sub.calledCount is 0
// i wonder if it's because the res.send() is inside a Promise code block???
// if I move the res.send() out from Promise, just before next(), then it works
it('should call res.send()', function() {
sinon.assert.calledOnce(resStub.send);
});
// this one works
it('should call next', function() {
sinon.assert.calledOnce(nextStub);
});
});
I going to choose #Amit answer as best answer since he helped and gave me clues on the changes.
I'm writing test cases with mocha and its hook beforeEach which deletes and re-creates all tables using sequelize.drop and sequelize.sync.
lib/testutils.js
exports.deleteAll = function () {
return sequelize.drop({logging: false, cascade: true}).then(function() {
return sequelize.sync({logging: false});
});
};
test/controllers/controllers.js
(A) This did work:
var testutils = require("../../lib/testutils");
describe("CRUD Controller", function() {
beforeEach(function(done) {
testutils.deleteAll().then(function(){
done();
}).catch(function(err) {
return done(err);
});
});
describe("#read()", function(){
it("should....", function(done) {
});
});
}
(B) This did not work:
var testutils = require("../../lib/testutils");
describe("CRUD Controller", function() {
beforeEach(function(done) {
testutils.deleteAll().then(done).catch(function(err) {
return done(err);
});
});
describe("#read()", function(){
it("should....", function(done) {
});
});
}
I don't understand why testutils.deleteAll().then(done) did not work and the first test case it("should....", function(done) did not wait for the beforeEach hook finished. I was getting TypeError: Converting circular structure to JSON. However, testutils.deleteAll().then(function(){ done(); }) did work.
My question is why (B) is not working while (A) is working? Any idea? Is there something wrong?
Mocha supports promises in beforeEach so just write below code:
beforeEach(function(){
return testutils.deleteAll();
});
Your deleteAll() function returns sequelize.sync() which returns a promise passing it this. I think the context will be the last model created, so when you pass a function to the returned promise, the function will be passed the last table created. So in case (A) you specified the function()
and called done() manually without passing any args to done(). But in case (B) when you pass done to the promise.then(), the last model created will be passed to done.
To get what I mean consider the following:
User.create({name: 'aName'}).then(function(user){console.log(user)});
is equivalent to
User.create({name: 'aName'}).then(console.log);
The user will be automatically passed to console.log().
So done was being passed the last model created which I think might cause a problem.
When you pass done directly it receive object returned from deleteAll, and mocha treat it as an error, so it try apply JSON.stringify to this object and show it as an error, but somehow JSON.stringify can't do it, so it throw exception which you can see.
You can clearly see it in Mocha::Runnable#run
I have been searching this site and the web for a while now and I cannot find a solution to this problem. I am trying to test the REST function of my API, but the PUT test never seems to work. Each time the test runs in mocha, I get the error "Uncaught assertion error: expected [] to equal {objectData}" where objectData is the json representation of the object I am trying to post (named couponTwo).
I have a feeling the problem lies in the beforeEach function, as it clears the database before each test, which needs to be done for many other tests to run correctly. Here is the test code:
var config = require('../config/config');
var mongoose = require('mongoose');
var should = require('should');
var request = require('supertest');
var Coupon = require('../models/coupon');
var url = require('../config/config').test.url;
process.env.NODE_ENV = 'test';
beforeEach(function (done) {
function clearCollections() {
for (var collection in mongoose.connection.collections) {
mongoose.connection.collections[collection].remove(function() {});
}
return done();
}
if (mongoose.connection.readyState === 0) {
mongoose.connect(config.test.db, function (err) {
if (err) throw err;
return clearCollections();
});
} else {
return clearCollections();
}
});
afterEach(function (done) {
mongoose.disconnect();
return done();
});
Here is the that is supposed to test that an object exists in the database after a PUT:
describe('#post', function () {
it('should return a coupon object after post', function (done) {
request(url).post('/coupons')
.set('Content-Type', 'application/json')
.send(couponTwo)
request(url).get('/coupons').end(function (err, res) {
if (err) throw err;
console.log(res.body);
res.body.should.eql(couponTwo);
done();
})
})
})
I apologize if the answer to this question is obvious and I am missing something fundamental, but I have reached a roadblock. Thanks for your help!
I think it is because of the asynchronous nature of request calls. You need to wrap the second request in a callback, so that it will only be executed when the first one is completed and your test object is put into the database.
Also, .eql(couponTwo) will fail in your case anyway, because your response is an array containing the object that was put, and you compare it directly to the object. Use .eql([couponTwo]) if you want to make sure that it is the only element in the array, or just use .containEql(couponTwo).
Try this:
request(url).post('/coupons')
.set('Content-Type', 'application/json')
.send(couponTwo)
.end(function () {
request(url).get('/coupons').end(function (err, res) {
if (err) throw err;
console.log(res.body);
res.body.should.containEql(couponTwo);
done();
});
});