I want to test a function returning a promise.
In this particular test, the promise is expected to be rejected with an Error object containing the classical message field (in this test, it is expected to equal "my error message") and a custom field I added named code, which is a string (like "EACCESS", "ERIGHT", etc, in this test it is expected to equal "EFOO")
I want to use chai-as-promised for that.
return expect(foo()).to.eventually.be.rejectedWith("my error message");
This assertion is working but now I would like to test the code field too.
How to do that?
If you're using Chai-As-Promised (as you say you are), then it allows for chaining off of rejectedWith - and it sets the chain assertion object to be the error object - meaning anything after rejectedWith() is now going to assert on the Error. This lets you do cool things like:
return expect(foo()).to.eventually
.be.rejectedWith("my error message")
.and.be.an.instanceOf(Error)
.and.have.property('code', 'EFOO');
Some of the chai methods also chain, so you can use that to make some quite deeply nested assertions about the error:
return expect(foo()).to.eventually
.be.rejectedWith("my error message")
.and.have.property('stack')
.that.includes('myfile.js:30')
Having version 5.1.0 of ChaiAsPromised, solution from Keithamus did not work for me - rejectedWith did not gave me the error object to assert, but "rejected" did:
return expect(foo())
.to.be.rejected
.and.be.an.instanceOf(Error)
.and.have.property('code', 'EFOO');
For asserting multiple properties
return expect(foo())
.to.be.rejected
.then(function(error) {
expect(error).to.have.property('name', 'my error message');
expect(error).to.have.property('code', 'EFOO');
});
#Markko Paas's solution didn't work for me until I added 'eventually', or else rejected value is always {} empty object.
return expect(foo())
.to.eventually.be.rejected
.and.be.an.instanceOf(Error)
.and.have.property('code', 'EFOO');
You can perform complex tests on errors using rejected.then:
it('throws a complex error', function () {
return expect(foo()).to.eventually.be.rejected.then((error) => {
expect(error.code).to.equal('expected code');
// other tests
// alternatively,
expect (error).to.eql({
foo: 'foo',
bar: 'bar
});
});
});
In my case, since I was using chai-as-promised in an async function, all I had to do is add an await statement before expect(promise).to.be.rejectedWith(errorMessage), e.g:
it('should reject', async () => {
await expect(promise).to.be.rejectedWith(errorMessage);
// ^^^^^
});
Chai-As-Promised did not work for me, because it does not throw if you expect something to be rejected and it does not reject.
Then I used the following, which IMO is also quite expressive:
//...
await $radioButton.click();
const executed = await(async () => {
try {
await tools.waitUntil(() => {
return consoleMessages.length === 2;
}, 1000); // 1000 is the timeout in milliseconds. waitUntil() rejects if it does timeout.
return true;
} catch (error) {
return false;
}
})();
chai.assert.strictEqual(executed, false);
Related
My scenario
I used to have some node.js implementation done using callbacks but I am now refactoring my code to use Promises instead - using Q module. I have the following update() function where the inner _update() function already returns a Promise:
exports.update = function(id, template, callback) {
if (!_isValid(template)){
return callback(new Error('Invalid data', Error.INVALID_DATA));
}
_update(id, template) // this already returns a promise
.then(function() {
console.log('UPDATE was OK!');
callback();
}, function(err) {
console.log('UPDATE with ERRORs!');
callback(err);
});
};
My question
I would like to achieve something like the following:
exports.update = function(id, template) {
if (!_isValid(template)){
// how could I make it return a valid Promise Error?
return reject(new Error('Invalid data', Error.INVALID_DATA));
}
return _update(id, template) // return the promise
.done();
};
Because _update() already returns a promise, I guess changing it this way would be enough (wouldn't be?):
return _update(id, template)
.done();
And... what about if the condition inside the if-clause equals true? How could I refactor
return callback(new Error('Invalid data', BaboonError.INVALID_DATA));
to throw an error to avoid passing the callback into update() and handling that error (or what ever error could ever be returning _update())?
Also, calling update():
myModule.update(someId, someTemplate)
.then(function() { /* if the promise returned ok, let's do something */ })
.catch(function(err) { /* wish to handle errors here if there was any */});
somewhere else in my code:
if there is an error during the promise propagation - it should handle it,
or, if there wasn't an error - it should do some other things
Am I close to what I am expecting? How could I finally achieve it?
I see only two problems.
If you want to explicitly return a rejected promise with a value, you should do that with Q.reject.
Calling .done() on promise means that the promise ends there. It cannot be chained further.
So, your code would look like this
exports.update = function (id, template) {
if (!_isValid(template)) {
return Q.reject(new Error('Invalid data', Error.INVALID_DATA));
}
return _update(id, template);
};
Now, the update function just returns a promise always. Its up to the callers to attach the success or failure handlers to it.
I'm writing a background job function on Parse.com CloudCode. The job needs to call the same function (that includes a Parse.Query.each()call) several times with different parameters, and I want to chain these calls with promises. Here's what I have so far:
Parse.Cloud.job("threadAutoReminders", function(request, response) {
processThreads(parameters1).then(function() {
return processThreads(parameters2);
}).then(function() {
return processThreads(parameters3);
}).then(function() {
return processThreads(parameters4);
}).then(function() {
response.success("Success");
}, function(error) {
response.error(JSON.stringify(error));
});
});
Below is the processThreads() function:
function processThreads(parameters) {
var threadQuery = new Parse.Query("Thread");
threadQuery... // set up query using parameters
return threadQuery.each(function(thread) {
console.log("Hello");
// do something
});
}
My questions are:
Am I chaining function calls using promises correctly?
What happens in threadQuery.each() returns zero results? Will the promise chain continue with execution? I'm asking because at the moment "Hello" never gets logged..
Am I chaining function calls using promises correctly?
Yes.
What happens in threadQuery.each() returns zero results? Will the promise chain continue with execution? I'm asking because at the moment "Hello" never gets logged.
I think I'm right in saying that, if "do something" is synchronous, then zero "Hello" messages can only happen if :
an uncaught error occurs in "do something" before a would-be "Hello" is logged, or
every stage gives no results (suspect your data, your query or your expectation).
You can immunise yourself against uncaught errors by catching them. As Parse promises are not throw-safe, you need to catch them manually :
function processThreads(parameters) {
var threadQuery = new Parse.Query("Thread");
threadQuery... // set up query using parameters
return threadQuery.each(function(thread) {
console.log("Hello");
try {
doSomething(); // synchronous
} catch(e) {
//do nothing
}
});
}
That should ensure that the iteration continues and that a fulfilled promise is returned.
The following example shows as use promises inside your function using a web browser implementation.
function processThreads(parameters) {
var promise = new Promise();
var threadQuery = new Parse.Query("Thread");
threadQuery... // set up query using parameters
try {
threadQuery.each(function(thread) {
console.log("Hello");
if (condition) {
throw "Something was wrong with the thread with id " + thread.id;
}
});
} catch (e) {
promise.reject(e);
return promise;
}
promise.resolve();
return promise;
}
Implementations of promise:
Web Browser https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise
jQuery https://api.jquery.com/promise/
Angular https://docs.angularjs.org/api/ng/service/$q
I have a function in JavaScript that uses the q library:
validateOnSelection : function(model) {
this.context.service.doLofig(model).then(function(bResult) {
if (bResult) {
return true;
} else {
throw new Error(that.context.i18n.getText("i18n", "error"));
}
});
}
How can I check in qunit that the result is error? Let's assume that the result: bResult is false and Error should raise.
I tried:
test("Basic test ", {
// get the oTemplate and model
return oTemplate.validateOnSelection(model).then(function(bResult) {
// Now I need to check the error
});
}));
The problem that I didn't get to the check "// Now I need to check the error"
There are lots of problems here. For one, you don't have any way to let the calling code know that your function has finished. Without that, QUnit can't determine when to run the assertions. Then you'll need to use QUnit's async ability, otherwise the test function finishes before your promise is resolved. Additionally, you can use the throws assertion to check for an error. The example below is using QUnit version 1.16.0 (the newest version).
validateOnSelection : function(model) {
// Instead, return a promise from this method which your calling code can use:
var deferred = Q.defer();
this.context.service.doLofig(model).then(function(bResult) {
if (bResult) {
// return true; this doesn't really do anything, it doesn't return anywhere.
// instead, resolve the promise:
deferred.resolve(true);
} else {
// we don't really want to "throw" here, we nee to reject the promise:
// throw new Error(that.context.i18n.getText("i18n", "error"));
deferred.reject(new Error(that.context.i18n.getText("i18n", "error")));
}
});
return deferred.promise;
}
Now we can set up our test to wait for the promise to finish and then test the result...
QUnit.test("Basic test", function(assert) {
// get the oTemplate and model
var done = QUnit.async(); // call this function when the promise is complete
// where does `model` come from???
oTemplate.validateOnSelection(model).then(function(bResult) {
// Now I need to check the error
assert.ok(bResult instanceof Error, "We should get an error in this case");
done(); // now we let QUnit know that async actions are complete.
});
});
I have the following method:
module.exports.getId = function(someObject) {
var myId = null;
return Q.Promise(function(resolve, reject, notify) {
// Loop through all the id's
someObject.user.player._id.forEach(function (id) {
if (id.root == "1.2.3.4.5.6") {
myId = id.extension;
}
});
resolve(myId);
});
};
This method works great as long as someObject exists and has the attributes user.player._id.
The problem i'm having is that if someObject is null or does not have all the appropriate nested attributes, an exception is thrown and the promise is never resolved. The only way I actually see the exception is if I have a .fail on the calling function, but that still doesn't actually resolve the promise.
Example of how I currently can see the exception:
myLib.getId.then(function() {
// something
}).fail(function(err) {
console.log(err);
});
I know 2 ways to get around this problem, but i'm not sure which, if either is the best way to handle something like this.
Option 1 (use try/catch inside my Q.promise):
module.exports.getId = function(someObject) {
var myId = null;
return Q.Promise(function(resolve, reject, notify) {
try {
// Loop through all the id's
someObject.user.player._id.forEach(function (id) {
if (id.root == "1.2.3.4.5.6") {
myId = id.extension;
}
});
} catch(e) {
reject(e);
}
resolve(myId);
});
};
Option 2 (explicitly check if someObject.user.player._id exists):
module.exports.getId = function(someObject) {
var myId = null;
return Q.Promise(function(resolve, reject, notify) {
ifi(someObject.user.player._id exists..) {
// Loop through all the id's
someObject.user.player._id.forEach(function (id) {
if (id.root == "1.2.3.4.5.6") {
myId = id.extension;
}
});
resolve(myId);
} else {
reject('invalid object');
}
});
};
Option 1 seems to smell funky to me because i'm using try/catch inside of a promise. Option 2 solves my problem, but any other unexpected exceptions will not get caught.
Is there a better way I should be handling this?
Your first example has a few problems:
When you catch an exception, you are rejecting the promise, then resolving the promise. That's breaking the promise contract; You can get around that by calling resolve within the try, not outside.
By using try/catch, you could be swallowing unintended errors. That is you are assuming that the only error would come from someObject.user.player._id not existing. That may be true at the moment, but it's not guaranteed to remain true as your code evolves.
By testing exactly for the known error condition, you know you won't be swallowing unexpected errors. Therefore, I would use your second example.
Given the following two $resource examples:
var exampleOne = $resource('/path').save(objectOne);
exampleOne.$promise.then(function (success) {}, function (error) {});
var exampleTwo = $resource('/path').save(objectTwo);
exampleTwo.$promise.then(function (success) {});
[NOTE: Example two contains no error handler]
And an interceptor that sits below all $http requests:
var interceptor = ['$location', '$q', function ($location, $q) {
function error(response) {
if (response.status === 400) {
return $q.reject(response);
}
else {
$location.path('/error/page');
}
return $q.reject(response);
}
return {
'responseError': error
};
}
$httpProvider.interceptors.push(interceptor);
How can I make the interceptor not reject when the example resources $promise.then() contain no error callback? If the call back exists as in exampleOne then I wish to reject, but if not as in exampleTwo then I wish to redirect to the error page thus changing the conditional to something like:
if (response.status === 400 && $q.unresolvedPromises.doIndeedExist()) { ...
Why? Because only some situations in my project call for handling a 400 in a user friendly way, thus I'd like to eliminate many duplicate error callbacks or having to place a list of uncommon situations in the interceptor. I'd like the interceptor to be able to decide based on the presence of another handler in the promise chain.
Simply put it is impossible, you can't detect if someone will attach a handler in some point in the future just like you can't tell if when you throw in a function it will be caught on the outside or not. However, what you want done can be done.
It is not a 'noob question', and it is very fundamental:
function foo()
throw new Error(); // I want to know if whoever is calling `foo`
// handles this error
}
First, what you can do
Simply put in the first case:
exampleOne.$promise.then(function (success) {}, function (error) {});
What you get is a promise that is always fulfilled. However, in the second case the promise might be rejected. Handling a rejection with a rejection handler is like a catch in real code - once you handle it it is no longer rejected.
Personally, I would not use an interceptor here, but rather a resource-using pattern since that's more clear with intent, you can wrap it in a function so it won't need a scope but I like that idea less. Here is what I'd do
attempt(function(){
return $resource('/path').save(objectTwo).$promise.
then(function (success) {});
});
function attempt(fn){
var res = fn();
res.catch(function(err){
// figure out what conditions you want here
// if the promise is rejected. In your case check for http errors
showModalScreen();
}
return res; // for chaining, catch handlers can still be added in the future, so
// this only detects `catch` on the function passed directly so
// we keep composability
}
Now, a short proof that it can't be done
Let's prove it for fun.
Let's say we are given the code of a program M, we create a new promise p and replace every return statement in M andthrow statement in M with a return p.catch(function(){}) and also add a return p.catch(function(){}), now a handler will be added to p if and only if running M ever terminates. So in short - given code M we have constructed a way to see if it halts based on an existence of a solution to the problem of finding if catch is appended to p - so this problem is at least as hard as the halting problem.
Maybe you can postpone redirect with zero timeout and give a chance to error handler if any exists to set flag on error object that error was handled:
var interceptor = ['$q', '$timeout', function ($q, $timeout) {
function error(rejection) {
return $q.reject(rejection).finally(function () {
$timeout(function () {
if (rejection.errorHandled === true) {
alert('all is under control');
} else {
alert("Houston we've got problems");
}
}, 0); //zero timeout to execute function after all handlers in chain completed
});
}
return {
'responseError': error
};
}];
var exampleOne = $resource('/path').save(objectOne);
exampleOne.$promise.then(function (success) { }, function(error) {
error.errorHandled = true;
});