In QUnit, it is possible to check for a promise to reject:
assert.rejects(Promise.reject(new Error("some error description")), "promise rejected");
However, there is no assert method to explicitly check for a promise to resolve. I know I can implicitly test for the promise to resolve like this:
QUnit.test("Promise will resolve", function(assert) {
return Promise.resolve().then(function() {
assert.ok(true);
});
});
In this case I'm not interested in any return value from the promise. The need for an assert.ok(true) (or specifying expect(0)) to prevent the "Expected at least one assertion" error, doesn't feel right. And when the promise rejects, I will not get a nice assert message along with the failed test.
Is there a reason something like assert.resolves(promise[, message]) doesn't exist? Is there some other way to explicitly test for a promise to resolve?
Related
I'm trying to assert on the request body to ensure the correct new test card is passed as part of the order.
it("User clicks confirm & pay button to complete order", () => {
cy.intercept("/api/checkout/payandcommit").as("placeOrder");
cy.placeOrderAndPay();
cy.wait("#placeOrder")
.its("response.statusCode")
.should("eq", 200)
.its("request.body")
.should("include", "cardNumber", 370000000000002);
});
All is good upto verfying the status code then it breaks.
This is the error thrown:
Timed out retrying: cy.its() errored because the property: request does not exist on your subject.
cy.its() waited for the specified property request to exist, but it never did.
If you do not expect the property request to exist, then add an assertion such as:
cy.wrap({ foo: 'bar' }).its('quux').should('not.exist')
If i comment out the status code assertion this new error is thrown: object tested must be an array, a map, an object, a set, a string, or a weakset, but object given.
Any help getting this working would be much appreciated!
Chaining the assertions like this doesn't work, because the subject changes inside the chain
cy.wait("#placeOrder") // yields interception object
.its("response.statusCode") // yields number 200
.should("eq", 200) // yields it's input (number 200)
.its("request.body") // not testing the interception object here
.should("include", "cardNumber", 370000000000002);
One way that works is to use a callback which gets the interception object
cy.wait('#placeOrder').then(interception => {
console.log(interception); // take a look at the properties
cy.wrap(interception.response.statusCode).should('eq', 404);
cy.wrap(interception.request.body)
.should("include", "cardNumber", 370000000000002) // not sure this should is correct
.should("have.property", "cardNumber", 370000000000002) // maybe this is better
})
You may also be able to use chained commands if the subject is maintained, which means you have to tweak the should() in the middle
cy.wait("#placeOrder")
.should('have.property', 'response.statusCode', 200)
.should('have.property', 'request.body.cardNumber', 370000000000002);
Check out the logged interception object to make sure you have the correct properties and property value types (e.g is cardNumber a number or a string?).
Thank you so much for your help Hiram K! I was able to get it working with:
cy.wait("#placeOrder").then((interception) => {
console.log(interception);
cy.wrap(interception.response.statusCode).should("eq", 200);
cy.wrap(interception.request.body.paymentDetails[0].cardNumber).should(
"include",
"370000000000002"
);
cy.wrap(
interception.request.body.paymentDetails[0].defaultPayment
).should("eq", false);
});
I'm new to promises, but as I understand it, .catch usually belongs at the end of a chain of promises:
promiseFunc()
.then( ... )
.then( ... )
.catch( // catch any errors along the chain )
What if the promises are split in between functions? Do I catch at the end of every function?
func1 () {
promiseFunc1()
.then((result) => {
promiseFunc2()
)
// should I .catch here?
}
func2 () {
func1()
.then((result) => {
// do stuff
})
.catch(console.log.bind(console)); // this also catches errors from func1
}
Maybe this is a symptom of another error (in which I'd love to hear if I'm doing this wrong), but when I try catching at the end of func1, I end up reaching the .then block of func2, with result = undefined. After deleting the catch in func1, it works -- but it feels wrong that func1 should expect any functions calling it to catch its errors.
.catch() works very much like try/catch. You should .catch() wherever you want or need to handle the error and either log something or change the course of the chain.
If all you want is for the promise chain to abort when an error occurs, then you can just put one .catch() at the very end of the chain and deal with the error there.
If, on the other hand, you have some sub-part of the chain that, if it has a rejection you want to do something different and allow the chain to continue or to take a different path, then you need to .catch() at that level where you want to influence things if there's an error.
All or Nothing Catch at the End
So, let's say you have four functions that all return promises.
If you do this:
a().then(b).then(c).then(d).then(finalResult => {
// final result here
}).catch(err => {
// deal with error here
});
Then, if anyone of your four promises rejects, then the rest of the chain will abort and it will skip to your one .catch(). For some operations, this is the desired behavior. If you intend to fetch some data from an external server, use that data to then fetch some other data from another external server and then write that data to disk, the whole process is pretty much all or nothing. If any of the earlier steps fails, there's nothing else you can do as the whole operation has failed, you may as well just use one .catch() at the end and report that error.
Intervening Catch to Change the Behavior Mid-Chain upon Error
On, the other hand suppose you have a situation were you want to fetch some data from an external server, but if that server is down, then you want to fetch the data from an alternate server. In that case, you want to catch the error from the first fetch and try something else. So, you'd use a .catch() on the very first step and attempt something different:
fetch1().catch(fetch2).then(b).then(c).then(finalResult => {
// final result here
}).catch(err => {
// deal with error here
});
Logging and Rethrow
When building sub-systems that others will use, it is often useful to log certain types of errors. So, even the whole promise chain might be all or nothing, you still may want to log an error earlier in the chain and then rethrow the error so that the chain continues in the rejected state:
function someFuncOthersUse() {
return a().then(b).then(c).catch(err => {
// you want your own logging any time a or b or c rejects
console.log("Error on fetchB", err);
throw err;
});
}
The caller will then be doing:
someFuncOthersUse().then(finalResult => {
// final result here
}).catch(err => {
// deal with error here
});
Gist:
I spy on get method of my rest service:
spyOn(restService, 'get').and.callFake(function () {
return deferred.promise;
});
The method I am trying to test is myService.getFormData() that returns a chained promise:
function getFormData() {
var getPromise = this.restService.get(endPoint, params, true);
var processedDataPromise = then(successHandle, failHandler);
return processedDataPromise;
}
Back to Jasmine spec, I invoke getFormData function and make assertions:
var processedDataPromise = myService.getFormData();
processedDataPromise.then(function(data) {
expect(data).not.toBeNull();
});
deferred.resolve(testFormData);
$rootScope.$digest();
The Problem:
The above derivative promise (processedDataPromise) does indeed get resolved. However the 'data' passed to it is undefined. Is it anything to do with $digest cycles not doing its job in Jasmine?
Why does Jasmine not pass any data to the above derived promise.?
Further Note: The processedDataPromise is a completely new promise than the get returned promise.
It is a promise for processedData which as we can see is returned by successHandle (Definition not shown) once its parent getPromise gets resolved.
In UI everything works like a Charm.
Sorry for posting the question. The derivative promise indeed got the resolved data I was referring to. The problem was that I was incorrectly accessing the JSON data inside of successHandle.
As a result the 'successHandle' returned null and the the processedDataPromise got back undefined response.
Stupid mistake, very hard to find, but the best part is the learning and understanding of JS Promises.
I'm not sure if I'm understanding a certain aspect of promises correctly and I couldn't find what I was looking for after a brief google/SO search.
Can a resolved promise returned to a rejected callback ever fire a resolved method later in the chain? Using jQuery deferreds (and it's deprecated pipe method) as an example:
x = $.Deferred();
x
.then(null,function() {
return $.Deferred().resolve();
})
.then(function() {
console.log('resolved');
},function() {
console.log('rejected');
});
The above code, logs rejected, I would've expected it to log resolved because the Deferred in the first error callback returns a resolved promise.
On the contrary, the same code using jQuery's deprecated pipe method logs, as I would've expected resolved.
x = $.Deferred();
x
.pipe(null,function() {
return $.Deferred().resolve();
})
.pipe(function() {
console.log('resolved');
},function() {
console.log('rejected');
});
Am I thinking about something wrong by trying to resolve a promise inside a rejected callback?
For anybody who has run into this same thought process, the following page helped me out: https://gist.github.com/domenic/3889970
Specifically:
...you can feed the return value of one function straight into another,
and keep doing this indefinitely. More importantly, if at any point
that process fails, one function in the composition chain can throw an
exception, which then bypasses all further compositional layers until
it comes into the hands of someone who can handle it with a catch.
It seems jQuery's pipe method was a violation of the Promise's specification.
I was writing a function that will be executed asynchronously. Suddenly a question popped up in my mind.
Let's say I have the following function for updating student records.
module.factory('StudentService', function($http, $q) {
var service = {};
service.updateStudent = function(studentData) {
var deferred = $q.defer();
$http.put('http://www.example.com/student/update', studentData, {
headers: { 'Content-Type': 'application/json' }
}).success(function(response) {
if (response.success) {
deferred.resolve(response.data);
} else {
// I am not resolving the deferred object here
}
}).error(function(response) {
// I am not rejecting the deferred object here
});
return deferred.promise;
};
return service;
});
I want to ask,
What will happen to the deferred object if it is not resolved or rejected?
If a deferred object is not resolved or rejected, will it result in error for chains like StudentService.updateStudent(data).then(...)?
Is there any practical usage of neither resolving nor rejecting a deferred object?
If deferred is not resolved or rejected, the respective handlers will never be called.
No error - just will not call successive handlers in the chain.
Too broad for SO
Off-topic:
You don't need to use $q.defer when you use functions that already return a promise (like $http) - this is called a deferred anti-pattern. Simply return the original promise (or the promise created with .then).
Is there any practical usage of neither resolving nor rejecting a deferred object?
Yes, ensuring that any callbacks of a chained .then() or .catch() are never called.
Thus, there are three possible outcomes :
promise becomes "resolved": promise chain follows the success path.
promise becomes "rejected": promise chain follows the fail path.
promise remains "pending": nothing downstream happens, but still could if (references to) the promise still exist and it becomes either resolved or rejected.
That said, it's bad practice to leave a promise hanging. You should endeavour always to resolve or reject.
Off topic but related ...
Like any js object, a promise will be garbage collected when there are no references to it. However, due to the nature of promises, it is often very difficult to delete all references - they are not always lexically represented (in the application code). For example, "hidden" reference(s) to promise(s) lurk in every promise chain even where no explicit assignment is made. Every promise, whether it is lexically represented or not, will occupy memory.
Back on topic ...
A promise that will never settle, should either :
not be created (not always possible)
be deleted (seldom simple)
Personally, I feel that a lot more work needs to be done in this area.