I'm getting unexpected timeout behavior using Mocha and Chai's 'expect' statement when a test fails.
The code:
require('./lib/test-env.js');
const expect = require('chai').expect;
const estimateQuery = require('../lib/estimate-query-helper.js');
describe('testing auth routes', function() {
describe('testing estimate query helper', function() {
it('should return an average daily rate and occupancy rate', (done) => {
estimateQuery.getEstimate()
.then(result => {
expect(result[0]['avg(`Average Daily Rate`)']).to.be.a('number');
expect(result[0]['avg(`Occupancy Rate LTM`)']).to.be.a('number');
done();
});
});
});
});
When I run this with correct expect values, test passes w/no timeout (and I've checked to see the returned values are all correct). But when I change 'number' to (for example) 'string' on either of the statements, rather than failing and throwing 'Expected ..., Actual ..." error, it times out. I've checked out the docs and Chai's open issues and can't find an answer.
Thanks very much in advance for your help.
That's because the promise is catching the error that's thrown by the failing expectation, resulting in the done callback not being called.
Mocha understands promises, so you can return a promise instead of using a callback:
describe('testing auth routes', function() {
describe('testing estimate query helper', function() {
it('should return an average daily rate and occupancy rate', () => {
return estimateQuery.getEstimate()
.then(result => {
expect(result[0]['avg(`Average Daily Rate`)']).to.be.a('number');
expect(result[0]['avg(`Occupancy Rate LTM`)']).to.be.a('number');
});
});
});
});
Any failed expectations will then result in the promise being rejected and the test being reported as failing.
Alternatively, you could stick with the done callback and add a catch:
describe('testing auth routes', function() {
describe('testing estimate query helper', function() {
it('should return an average daily rate and occupancy rate', (done) => {
estimateQuery.getEstimate()
.then(result => {
expect(result[0]['avg(`Average Daily Rate`)']).to.be.a('number');
expect(result[0]['avg(`Occupancy Rate LTM`)']).to.be.a('number');
done();
})
.catch(done);
});
});
});
Related
I am trying to implement mocha testing, however it doesn't seem to work the way I expect it to. My code looks like the following:
async function getFoo() {
return new Promise(resolve => {
setTimeout(() => {
resolve('resolved')
}, 2500)
})
}
describe(`Testing stuff`, function () {
it('resolves with foo', () => {
return getFoo().then(result => {
assert.equal(result, 'foo')
})
})
})
Error message:
Error: Timeout of 2000ms exceeded. For async tests and hooks, ensure "done()" is called; if returning a Promise, ensure it resolves.
To me it seems like I have to increase the threshold of the timeout.
Is there a better way to do this? Since I don't know how long each test might take. I am testing nodejs child processes
best regards
As it is described in the error you need to use done() end of your test.
function delay(timeout = 2500) {
return new Promise(resolve => {
setTimeout(() => {
resolve('resolved')
}, timeout)
})
}
describe(`Testing stuff`, function (done) {
it('resolves with foo', (done) => {
return delay().then(result => {
assert.equal(result, 'foo')
done()
})
})
});
// better readability
describe(`Testing stuff`, function (done) {
it('resolves with foo', (done) => {
const result = await delay();
assert.equal(result, 'foo')
done();
})
});
But as you described you are testing child_processes which I don't recommend. Because to test child_process you need to promisify the child_process which doesn't make sense. Check here for more info How to promisify Node's child_process.exec and child_process.execFile functions with Bluebird?
If you really want to test child_process maybe you can mock the process with the link i gave but only for testing purposes.
I'm just starting learning mocha and chai. And I get stuck here
const UserService = new Service();
describe("user-services.spec", () => {
describe("Services testing", () => {
before((done) => {
db();
userModel.remove({}, done);
})
after((done) => {
mongoose.connection.close();
done();
})
it("should add user to db", () => {
let id = 4;
let name = "Alen";
(async () => {
let result = await UserService.addUser(id, name);
console.log("result", result);
result.should.have.property("_i");
//done();
})();
})
})
})
Now I have two question based on above code
Is that this test case always pass even if I change "_id" to "_i" I don't know how ?
If I want to use done with above code and uncomment the done() then it gives error
Timeout of 2000ms exceeded. For async tests and hooks, ensure "done()" is called; if returning a Promise, ensure it resolves.
For your first question, I've tested using result.should.have.property("_i"); and it fails.
I've mocked the method like this:
async function addUser(id, name){
return {_id:""}
}
And it throws
Uncaught AssertionError: expected { _id: '' } to have property '_i'
As expected.
So check the value returned. Also you can check chai documentation
And for the second question, done() is the callback to say Mocha where the function is done. Also Mocha has a timeout (max. time the test will be waiting). If the maximum time is reached without a done() calling, Mocha will thrown an error saying it has not been finished (done is not called).
If you don't call done() Mocha will stuck waiting without knowing when the function is completed.
You get the error Timeout of 2000ms exceeded because 2 seconds is the default value as documentation says.
Specifies the test case timeout, defaulting to two (2) seconds (2000 milliseconds). Tests taking longer than this amount of time will be marked as failed.
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)
})
Test cases(Test1, Test2) execute before promise get the data. This is the file mockExecution.js
describe('AC 1: This is suite one', ()=>
{
before((done)=>
{
promiseResp.then((data) => {
console.log("i am in the promise");
responseData = data;
process.exit(0);
}, (err) => {
console.log('promiseResp.err', err);
process.exit(1);
})
done();
})
it('Test1', (done)=>
{
expect(responseData.measure.abc).not.toBe(responseData.measure_list.abc);
done();
});
it('Test2', (done)=>
{
expect(responseData.measure.abc).not.toBe(responseData.measure_list.abc);
done();
});
});
PromiseResp inside the Before block doesn't execute. Therefore "responseData" variable doesn't have data and it throws test case failed. I guess there is some asynchronous time issue, but don't know how to resolve it and also where do i put this "process.exit(0)". Below is the actual output:
AC 1: This is suite one
I am in the before
1) Test1
2) Test2
0 passing (7ms)
2 failing
1) AC 1: This is suite one
Test1:
TypeError: Cannot read property 'measure' of undefined
at Context.it (QA/mockExecution.js:160:29)
2) AC 1: This is suite one
Test2:
TypeError: Cannot read property 'measure' of undefined
at Context.it (QA/mockExecution.js:167:29)
[process business logic and prints some logs here, i can't paste here]
finished analyzing all records
i am in the promise
npm ERR! Test failed. See above for more details.
I am expecting output in the following sequence:
[process business logic and prints some logs here, I can't paste here]
finished analyzing all records
AC 1: This is suite one
I am in the before
I am in the promise
1) Test1 passed
2) Test2 paseed
You need to call done within your then & after you actually
assigned responseData = data:
before((done) => {
promiseResp.then((data) => {
responseData = data;
// Promise has resolved. Calling `done` to proceed to the `it` tests.
done();
})
.catch((err) => {
// Calling `done` with a truthy `err` argument, in case
// the promise fails/rejects, to fail-early the test suite.
done(err);
})
})
otherwise before ends prematurely and proceeds to the next tests, before the promise actually resolves and assigns your responseData variable.
Here's a working example using the before hook:
const expect = require('chai').expect
const getFooValue = () => {
return new Promise(resolve => {
setTimeout(() => {
resolve('foo')
}, 500)
})
}
describe('#getFooValue()', function () {
let responseData
before(done => {
getFooValue().then(data => {
responseData = data
done()
})
.catch(err => {
done(err)
})
})
it('response has a value of foo', () => {
expect(responseData).to.equal('foo');
})
it('response is a String', () => {
expect(responseData).to.be.a('String');
})
})
What you're doing now is:
You define the Promise.
You (prematurely) call done and Mocha proceeds to execute the it tests.
The it tests run while responseData is still undefined.
The Promise within before eventually resolves and assigns the responseData variable.
...but at that point it's too late. The tests have already run.
The use of done together with promises is an antipattern because this often results in incorrect control flow, like in this case. All major testing frameworks already support promises, including Mocha.
If default timeout (2 seconds) is not big enough for a promise to resolve, timeout value should be increased, e.g. as explained in this answer by setting it for current test suite (this in describe context). Notice that arrow function should be replaced with regular function to reach suite context.
It should be:
describe('AC 1: This is suite one', function () {
this.timeout(60000);
before(() => {
return promiseResp.then((data) => {
responseData = data;
});
});
it('Test1', () => {
expect(responseData.measure.abc).not.toBe(responseData.measure_list.abc);
});
...
No need for catch for a promise; promise rejections will be handled by the framework. No need for done in tests; they are synchronous.
There's no promise in your it(), so no reason for done(), but it should be called inside then() as it is a callback.
And overall it's cleaner to use async/await. It doesn't work well in before() though.
Also 'function()' is preferable in describe() to set timeout for the tests (Invoking it as a chained method never worked on my experience)
describe('AC 1: This is suite one', function() {
this.timeout(12000); //12 sec timeout for each it()
before((done) => {
promiseResp().then((data) => {
responseData = data;
done();
})
})
it('Test1', () => {
expect(responseData.measure.abc).not.toBe(responseData.measure_list.abc);
});
it('Test2', () => {
expect(responseData.measure.abc).not.toBe(responseData.measure_list.abc);
});
});
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;
})...