-
Notifications
You must be signed in to change notification settings - Fork 781
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add assert method for rejection of promises #1204
Comments
I'm 👍 on this. Should include a docs update with it as well. |
I like the idea of consistency here (return to It's there today because QUnit needs to trigger the code and catch an exception. When dealing with async code or promises, this isn't needed. What about passing the promise directly? QUnit.test('example', function (assert) {
let result = asyncFunction();
assert.throws(result, /NOOOOOO/, 'message');
});
QUnit.test('example 2', function (assert) {
let result = new Promise( (resolve, reject) => {
setTimeout(reject, 10, new Error('NOOOOOOOO!!!!!'));
});
assert.throws(result, /NOOOOOO/, 'message');
}); |
After thinking about this a big more, I'm actually unsure that augmenting In particular, I am concerned that by wrapping promises in With those in mind, is there a compelling reason to need this feature instead of simply using |
Hmm. I think that same logic applies to Ultimately, the reason we need support for rejected promise assertions is the same reason we need tldr; it is very error prone to do the right thing. test('...', function(assert) {
return somethingReturningAPromise()
.then(() => assert.notOk(true, 'should have rejected with "some message"'))
.catch((reason) => assert.equal(reason.message, "some message"));
}); ^ is annoying code to write, and this is why we have libraries. 😃 |
Could we create new APIs for promises around asserting that a promise resolves or rejects (possibly supporting a predicate that could be given the resolve/reject arguments and could further validate the result)? That way we can just keep the APIs for promise rejection and exception catching separate. |
So that makes sense, but I think I'd prefer a separate helper for these scenarios. Maybe assert.throws(() => foo());
assert.throws(() => bar());
assert.throws(baz); It is not possible to tell which of the above is asynchronous, which, as I think we've seen in the Ember community, can lead to an awful lot of confusion. It also means that when a Promise is returned we will either have to automatically pause test execution or return a Promise. The former is likely to be relatively confusing as we couldn't prevent execution of code coming after the I'd much rather have something like the following: return assert.rejects(promise, /value to compare/, 'message'); // returns a promise |
@trentmwillis I don't know if you were replying to my comment or that of @rwjblue, but I basically like your last proposed API. The only issue I have with it is it seems the regular expression would have to be run against the first reject argument, where there could be multiple. So I would want to add a callback form that could take all rejection arguments. But otherwise I really like that API. |
Just to be clear, I'm not really saying that I think |
@platinumazure, sorry, was replying to @rwjblue (timing issues). Also, my understanding is that Promises can only be resolved/rejected with a single value. @rwjblue, okay 👍, so it sounds like adding a new API to specifically improve Promise ergonomics is inline with both of our thinking. |
@trentmwillis - Generally speaking that API seems good to me. Though we should discuss the exact interface (e.g. should the return value always resolve?)... |
Oops @trentmwillis, you're right. My mistake. |
Right, they only have one value but it isn't obvious what property folks are wanting to assert against. Oftentimes the rejection value may not be an error instance. |
FWIW, I implemented QUnit.assert.rejects = function(callback, expected, message) {
this.test.ignoreGlobalErrors = true;
let actual;
let result = false;
let done = this.async();
return Promise.resolve()
.then(callback)
.then(null, reason => {
actual = reason;
})
.finally(() => {
if (actual) {
const expectedType = typeof expected;
// We don't want to validate thrown error
if (!expected) {
result = true;
expected = null;
// Expected is a regexp
} else if (expected instanceof RegExp) {
result = expected.test(errorString(actual));
// Expected is a constructor, maybe an Error constructor
} else if (expectedType === 'function' && actual instanceof expected) {
result = true;
// Expected is an Error object
} else if (expectedType === 'object') {
result =
actual instanceof expected.constructor &&
actual.name === expected.name &&
actual.message === expected.message;
// Expected is a validation function which returns true if validation passed
} else if (expectedType === 'function' && expected.call(null, actual) === true) {
expected = null;
result = true;
}
}
this.pushResult({
result,
actual,
expected,
message,
});
this.test.ignoreGlobalErrors = false;
})
.finally(() => {
this.test.ignoreGlobalErrors = false;
done();
});
}; @trentmwillis - Does this seem like a reasonable path forward? |
Hopefully that seemed good, I submitted #1238 with the implementation and docs... |
TL;DR: LGTM! I agree the current workaround is worth trying to avoid by providing a built-in convenience method. (The workaround being the idea of returning the promise to At first, I wasn't sure whether However, I see now that the implementation goes beyond inverting the promise and attaching an assertion to the chain, it also adds async tracking. That was the missing puzzle piece. It does make for a more complex method, but I think in this case that overhead is worth it and makes for a very intuitive method. One thing to keep in mind here is that this further increases the number of async-tracking methods we have in the public API. Projects aiming to reduce or avoid multiple async things during tests, will need to keep in mind that |
Thanks for this tip, I'll have to see if I need to make any changes in eslint-plugin-qunit for this. That said, I think the implementation of |
There is definitely still async involved (which I think folks will understand with the verbiage used here?) that folks should be aware of (e.g. no guaranteed ordering of the rejection assertions), but yes, the idea here is definitely to hide away the manual shenanigans that folks have to do today when testing rejections. |
assert.throws
should respect returned promises
This would support the following:
An alternative would be to add a
assert.rejects
, but I feel like transparently supporting this case inassert.throws
is more aligned with how we handle things in other areas.The text was updated successfully, but these errors were encountered: