I am using Chai http and the promise. The following test should fail, but it passes without ever calling the then function. If I add the done parameter to wait for the async function to finish, it fails (correctly). Am I doing something wrong?
it('Returns the correct amount of events', function() {
chai.request(app)
.get('/api/events/count')
.then(function(res) {
throw new Error('why no throw?');
expect(res).to.have.status(200);
expect(res).to.be.json;
})
.catch(function(err) {
throw err;
});
});
When you forget to return promise your test is evergreen. So, you just need to return promise to make it work:
it('Returns the correct amount of events', function() {
return chai.request(app)
.get('/api/events/count')
.then(function(res) {
throw new Error('why no throw?');
expect(res).to.have.status(200);
expect(res).to.be.json;
})
.catch(function(err) {
return Promise.reject(err);
});
});
Related
Scenario
I'm trying to do multiple it specs on a single external load rather than have the external data loaded EVERY time.
Question
How can I do this with a single call of getExternalValue while still keeping my it definitions?
Ideas
Currently I'm doing all the expects in a single it block. I've also thought about storing the loaded value before my tests but then I'd have to find another way to make jasmine wait until the value is loaded.
Code
function getExternalValue(callback) {
console.log("getting external value");
setTimeout(function() {
callback(true);
}, 2000);
return false;
}
describe("mjaTestLambda()", function() {
it("is truthy", function(done) {
let truthy;
truthy = getExternalValue(function(bool) {
truthy = bool;
expect(truthy).toBeTruthy();
done();
});
});
it("is falsy", function(done) {
let truthy;
truthy = getExternalValue(function(bool) {
truthy = bool;
expect(!truthy).toBeFalsy();
done();
});
});
});
How can I do this with a single call of getExternalValue while still
keeping my it definitions?
Use beforeEach() or beforeAll() to get the resolved value. Personally I suggest beforeEach() as it will reset the value for each test and helps ensure a clean setup for each one.
I noticed your function has a callback parameter. Async/await is a useful pattern that works best when (1) you're writing async/await functions or (2) your functions return a Promise. If you need to keep the callback parameter, let me know and I'll update the following:
// returns Promise
function getExternalValue() {
return new Promise((resolve, reject) => {
console.log("getting external value");
setTimeout(() => {
resolve(true);
}, 2000);
});
}
describe("mjaTestLambda()", () => {
let value;
beforeAll(() => {
return getExternalValue()
.then((v) => { value = v; });
});
it("is truthy", () => {
expect(v).toBeTruthy();
});
it("is not falsy", () => {
expect(!v).toBeFalsy();
});
});
This question already has answers here:
node.js how to get better error messages for async tests using mocha
(2 answers)
Closed 5 years ago.
While writing tests for a project with Mocha & Chai I noticed I could get true.should.be.false to fail, but when the variable under test came from a promise and that expectation failed, Mocha would time out: Error: Timeout of 2000ms exceeded. For async tests and hooks, ensure "done()" is called; if returning a Promise, ensure it resolves.
Here are the examples of things I tried (and the solution) in hopes that it'll help someone in the future.
const chai = require('chai');
const should = chai.should();
const assert = chai.assert;
function getFoo() {
return new Promise((resolve, reject) => {
resolve({bar: true});
});
}
describe('Example for StackOverflow', function() {
it('will fail as expected', function() {
true.should.be.false;
});
it('will also fail as expected', function() {
var foo = {
bar: true
};
foo.bar.should.be.false;
});
it('times out instead of fails', function(done) {
getFoo().then(data => {
data.bar.should.be.false;
done();
});
});
it('times out instead of fails even without arrow notation', function(done) {
getFoo().then(function(data) {
data.bar.should.be.false;
done();
});
});
it('should throws an error when the expectation fails, but the done() in catch() doesnt seem to matter', function(done) {
getFoo().then(data => {
data.bar.should.be.false;
done();
})
.catch(error => {
console.error(error);
done();
})
.catch(error => {
console.error(error);
done();
});
});
it('still throws an error in the catch() if I try to use assert.fail() inside the catch to force a failure', function(done) {
getFoo().then(data => {
data.bar.should.be.false;
done();
})
.catch(error => {
console.error(error);
assert.fail(0, 1);
done();
})
.catch(error => {
console.error(error);
done();
});
});
});
For reference, here are the versions at play here:
node: v5.12.0
chai: v4.1.0
mocha: v3.4.2
This is different from node.js how to get better error messages for async tests using mocha in that I'm specifically talking about a timeout that occurs when should detects a failure and throws an error that isn't caught because the promise is not returned. Their solution focuses on using done- mine does not need it because it's returning the promise to Mocha so that it can catch the error.
The solution is to return the promise in the test function - I wasn't doing that in my times out instead of fails examples, and I noticed it later while writing up my examples to ask this question.
const chai = require('chai');
const should = chai.should();
const assert = chai.assert;
function getFoo() {
return new Promise((resolve, reject) => {
resolve({bar: true});
});
}
describe('Example for StackOverflow', function() {
it('needs to return the promise for the test runner to fail the test on a thrown exception - no done necessary', function() {
return getFoo().then(data => {
data.bar.should.be.false;
});
});
});
I am writing integration tests using mocha and chai. I want to use mocha -w however whenever the code re-runs due to changes, the tests fails with the message that done was not called. However every time that I stop mocha and run again it runs fine. There seems to be some type of scoping issue, that when mocha re-runs some things are out of scope. Any help would be appreciated!
Using Mocha "mocha#3.2.0"
function sendGoodMsg(){
return sendMsg(payloads.goodPayload, "good_msg")
}
function tooManyMsg(){
return sendMsg(payloads.tooManyPayload, "too_many_msg")
}
function tooFewMsg(){
return sendMsg(payloads.tooFewPayload, "too_few_msg")
}
function sendMsg(payload, type){
//get process started
let sentMsg = new queue.CCMessage( 'scheduler_service', queue.WORK, 'setSchedule' );
sentMsg.context.origContext = uuid.v4();
sentMsg.context.testType = type;
sentMsg.payload = payload;
return sentMsg.Send();
}
function receiveMsg(payloadType){
return new Promise(resolve =>{
const messageReceiver = new queue.MessageReceiver(queue.THIS_SERVICE, queue.WORK);
messageReceiver.on('setSchedule', (msg)=>{
if(msg.context.testType === payloadType){
resolve(msg.payload);
msg.ACK();
} else {
msg.NAK();
}
});
messageReceiver.Listen();
})
}
describe("should receive sent message", function(){
it('should have the following fields', function () {
return sendGoodMsg().then(()=>{
return receiveMsg("good_msg").then(payload =>{
return assert.isTrue(processMessage.messageIsValid(payload));
})
})
})
it('should not have extra fields', function () {
return tooManyMsg().then(()=> {
return receiveMsg("too_many_msg").then(payload => {
return assert.isFalse(processMessage.messageIsValid(payload));
})
})
})
it('should not be missing fields', function () {
return tooFewMsg().then(()=> {
return receiveMsg("too_few_msg").then(payload => {
return assert.isFalse(processMessage.messageIsValid(payload));
})
})
})
});
I'm trying to save the return value of $http service inside my controller, but I get "undefined" like response
In my controller, I call a service that uses the $http:
//this returns undefined
vm.user_instruments = instruments.getInstruments();
My service:
function instruments($http){
this.getInstruments = function(){
$http.get('url/').
then(function(response) {
/*this console.log print the response,
but this value I can't get it in my controller*/
console.log(response.data);
return response.data;
}, function(error) {
return error.data;
});
}
}//end service
So, what am I doing wrong? My purpose is that the controller be ignorant of any details of HTTP
Several problems . First your service function isn't returning anything .... return $http from it.
this.getInstruments = function(){
// return the request promise
return $http.get('url/').
then(function(response) {
return response.data;
}, function(error) {
return error.data;
});
}
Then in controller assign the scope inside a promise callback:
instruments.getInstruments().then(function(data){
vm.user_instruments = data
});
you have two options to do this:
1. return the promise to the controller and use the promise in the controller
function service ($http) {
this.request = function () {
return $http.request({ /*...*/ });
};
}
function controller (service) {
service.request().then(function (resp) {
console.log(resp);
});
}
2. send callback to service and return the data to the callback
function service ($http) {
this.request = function (callback) {
return $http.request({ /*...*/ }).then(function (resp) {
callback(null, resp);
}, function (err) {
callback(err);
});
};
}
function controller (service) {
service.request(function (err, resp) {
if (err) return console.log(err);
console.log(resp);
});
}
the popular option is to use promises, so use option 1 :)
Try this way
Service:
function instruments($http){
this.get = function(callback){
$http.get('/url').success(function(res){
callback(res);
});
}
} /* end service */
Controller:
instruments.get(function(res){
vm.instruments = res;
});
It should work.
PS: typed in mobile.
I'm trying to fill some local data resolving a series of remote calls.
When every promise is resolved, I load the data and proceed.
The method $q.all( [] ) does exactly this:
$q.all([
this.getUserInfo(11)
.then(function (r) {
results.push(r)
}),
this.getUserConns()
.then(function (r) {
results.push(r)
}),
this.getUserCtxs()
.then(function (r) {
results.push(r)
})
])
.then(function () {
console.log(results)
})
Problem is, this code is not resilient.
If any of these call fails, nobody gets the fish!
Wrapping the calls in a try/catch statement, simply causes $q.all() to entirely ignore the entry, even when not failing (note the console.log in the func)...
$q.all([
this.getUserInfo2(11)
.then(function (r) {
results.push(r)
}),
function () {
try {
this.getUserGroups()
.then(function (r) {
console.log(r)
results.push(r)
})
}
catch (err) {
console.log(err)
}
},
])
.then(function () {
console.log(results)
})
Output:
[Object]
Any hint on how I could wrap this to be resilient?
Thanks to #dtabuenc, I've gone one step further.
Implementing the error callback, I can avoid the breaking of the chain, and push the values of the resolved promises.
However, a nasty Exception is still displayed on the console...
How can I get rid of that if I cannot try/catch on async requests?
Caller code
return $q.all([
this.getUserInfo(user_id)
.then(function (r) {
results['personal_details'] = r
}),
this.getUserConns()
.then(
function (r) {
results['connections'] = r
},
function(err) {
console.log(err)
})
])
.then(function () {
return (results)
})
Callee code (inject with an exception)
getUserConns: function() {
return __doCall( ws.getUserConnections, {} )
.then( function(r) {
// very generic exception injected
throw new Error
if (r && r.data['return_code'] === 0) {
return r.data['entries']
}
else {
console.log('unable to retrieve the activity - err: '+r.data['return_code'])
return null
}
})
},
This will work but also push the errors to the array.
function push(r) {
results.push(r);
}
$q.all([
this.getUserInfo(11).then(push).catch(push),
this.getUserConns().then(push).catch(push),
this.getUserCtxs().then(push).catch(push)
])
.then(function () {
console.log(results);
})
You should also improve your understanding of promises, you never should use try-catch with promises - when using promises, you use the .catch() method (with everything else being implicitly a try). This works for normal errors as well as asynchronous errors.
If you want to totally ignore the errors:
function push(r) {
results.push(r);
}
function noop() {}
$q.all([
this.getUserInfo(11).then(push).catch(noop),
this.getUserConns().then(push).catch(noop),
this.getUserCtxs().then(push).catch(noop)
])
.then(function () {
console.log(results);
})
I think it's easier to do :
$q.all([
mypromise1.$promise.catch(angular.noop),
mypromise2.$promise.catch(angular.noop),
mypromise1.$promise.catch(angular.noop)
])
.then(function success(data) {
//.....
});
I'm not sure what you mean by resilient. What do you want to happen if one of the promises fails?
Your try-catch won't work because the promise will fail asynchronously.
You can however pass in an error handler as the second parameter to the then() call and do whatever you wish there.
Same issue here. For those of you with for loops: inside a then response:
var tracks = [];
var trackDfds = [];
for(var i = 0; i < res.items.length; i++){
var fn = function () {
var promise = API.tracks(userId, res.items[i].id);
return promise.then(function (res) {
if (res.items.length) {
tracks.push(res.items);
}
}).catch(angular.noop);
};
trackDfds.push(fn());
}
$q.all(trackDfds)
.then(function (res) {
console.log(tracks);
});
#Esailija's answer seems like a workaround to a problem.
You can't resolve the problem outside the main contributor to the problem: $q.
It seems a bit wiser to have reject callbacks for each then (2nd argument) and in there to insert $q.reject(...).
Example:
$q.all([
this.getUserInfo(11).then(
function (response) { // UI data preparation for this part of the screen },
function (response) {
$q.reject(response);
}
),
// ...
])
.then(
function () {
// all good
},
function () {
// at least one failed
}
)
This is particularly indicated when the UI model depends on all ajax calls.
Personally I think this is the safe way to proceed anyway, because most of the times you do want to push some server messages to some toast component on the reject callbacks, or alert the user in some way (queuing 7 ajax calls doesn't mean you can't show anything because 1 failed - it means you won't be able to show some region of the screen - that needs a specialized feedback to the user).