Mocha test it() terminate before Promise resolved. (Timeout) - node.js

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???

Related

How to do test after connection with mongoose to atlas

I want to test create user, so after connection to the DB I want to delete all the users that I tested and after it, I want to create new for the test.(Mocha)
test_helper.js
mongoose.connect(uri, { useNewUrlParser: true, useUnifiedTopology: true });
mongoose.connection
.once('open', () => {
console.log("connected")
})
.on('error', (error) => {
console.warn('Warning', error)
});
beforeEach((done) => {
mongoose.connection.collections.users.drop(() => {
done();
}
)
})
create_test.js
describe('Creating', () => {
it('saves a user', () => {
const testUser = new User({ name: 'Test' });
testUser.save();
});
});
I am getting the next error
Error: Timeout of 2000ms exceeded. For async tests and hooks, ensure "done()" is called; if returning a Promise, ensure it resolves.
what do I miss?
Some topics before my answer:
Asynchronous coding:
If you don't know/ are sure how it works, I recommend you to stop and learn about it: callback,Promises, async / await.
Testing:
Basically the flow is : create some scenario and assert some case, for example the attached code. I created a user and tested if it really work.
Testing Asynchronous code: after you read about callback function you can understand that done() is a callback function that permit finish the current async. function and pass to the next async. function.
testUser.save() returns a Promise and you aren't handling it.
...
testUser.save().then(()=>{
assert(testUser.isNew === false)
done();
}
...
It should work, but if you want to test some scenarios one after other you should handle it.
describe('Creating', () => {
it('some test', (done) => {
// some logic
done()
}
it('another test', (done) => {
// some logic
done()
}
});
});

Test for .catch(() block fails in Jest

I am trying to test that a Vuex action's .catch(() block is reached given a certain API response, and that it returns the error. The catch is reached, but the test fails since it is expecting the actual API response, instead of the error that I throw.
The action that I am testing is:
getPageItems ({ commit, state, }) {
const page = state.page;
return testApi.fetch(`${pageNumber}`).then((response) => {
try {
isValid(response);
commit('addItemsToList', response);
} catch (error) {
console.error(error);
}
},
export const isValid = (response) => {
response.name ? true : throw new Error('invalid item');
};
The test I have is:
test('errors caught', async () => {
const item = {};
const commit = jest.fn();
const state = {
pageNumber: 2,
};
testApi.fetch.mockRejectedValue(item);
expect.assertions(1);
await getPageItems({ commit, state, }).catch((e) => expect(e).toBe('invalid item');
});
This test fails, as it expects e to be item (the response), and not the error. I'm not sure why this is the case.
mockApi.get.mockResolvedValue(item) results in fulfilled promise, none of catch callbacks will be called.
catch makes getPageItems unconditionally resolve with fulfilled promise, another catch callback after getPageItems() will never be called. It doesn't cause bad response error either. getPageItems returns a fulfilled promise and conditionally calls console.error, this is what needs to be tested.
This test doesn't return a promise, even if a rejection was asserted, it would be ignored. async..await is the way to chain promises correctly:
test('errors are caught', async () => {
mockApi.get.mockResolvedValue();
jest.spyOn(console, 'error');
await getPageItems({ commit, state });
expect(console.error).toHaveBeenCalledWith('bad response'));
});

Mocha test cases executes before promise gets the data

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);
});
});

new Builder().forBrowser('chrome').build() hangs indefinitely

I am using mocha and selenium to do end to end integration testing. I am using beforeEach and afterEach hook to initialize browser and kill it after each test. Here is my js file which has both the methods
require('chromedriver')
// globals
global.assert = require('assert')
global.driver = null
const { Builder, By, until, promise } = require('selenium-webdriver')
// setup
beforeEach(async function () {
this.timeout(utils.DEFAULT_TEST_TIMEOUT)
while (driver !== null) {
console.log('waiting for driver to quit from last test')
await utils.sleep(1000)
}
try {
driver = await new Builder().forBrowser('chrome').build()
await driver.manage().window().setSize(1600, 900)
} catch (ex) {
console.log(ex.stack)
}
})
afterEach(async function () {
this.timeout(utils.DEFAULT_TEST_TIMEOUT)
if (driver === null) {
console.log('some problem in before each of the test ' + this.currentTest.title + ' returning...')
return
}
await saveScreenShot(SCREENSHOTS_PATH + this.currentTest.parent.title, this.currentTest.title)
await driver.quit()
driver = null
})
Now when i run the test suit on jenkins i very frequently see this ("email already registered" is a test name)
✓ email domain not allowed (4167ms)
1) "before each" hook for "email already registered"
some problem in before each of the test email already registered returning...
39 passing (9m)
1 pending
1 failing
1) "before each" hook for "email already registered":
Error: Timeout of 60000ms exceeded. For async tests and hooks, ensure "done()" is called; if returning a Promise, ensure it resolves.
The thing to notice is that once this error comes the subsequent tests do not run. My test suit has 100s of tests and in the run above it quit once it encountered this beforeEach error and didn't run next set of tests.
I have tried various things but nothing seems to work. Any pointer in debugging this will be very helpful. Thanks in advance.
The beforeEach is never resolving and causing the test to timeout. You must signify either through a resolved promise or calling a done() callback function that the beforeEach has finished.
You can do either one of the following:
wrap the body of the beforeEach in returning a new Promise as such:
beforeEach(async function () {
return new Promise(async (resolve,reject) => {
this.timeout(60000)
try {
driver = await new webdriver.Builder().forBrowser('chrome').build()
await driver.manage().window().setSize(1600, 900)
resolve()
} catch (ex) {
reject(ex.stack)
}
})
})
OR you can add the done() callback function to your beforeEach definition:
beforeEach(async function (done) {
this.timeout(60000)
try {
driver = await new webdriver.Builder().forBrowser('chrome').build()
await driver.manage().window().setSize(1600, 900)
done();
} catch (ex) {
done(ex.stack)
}
})
I didn't test these, but either of them should work.

Mocha/Sinon test mongoose inside express

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;
})...

Resources