-
-
Notifications
You must be signed in to change notification settings - Fork 4.2k
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
Allowing rejected promises in tests #11469
Comments
This only happens when I do the stubbing in |
seems dubious to allow rejections during tests |
Yeah, understand that. But isn't the use case actually valid? |
I believe I'm running up against this in my acceptance tests for ESA and ESA devise. What I have is describe('with invalid login', function() {
beforeEach(function() {
this.options.status = 401;
TestHelper.stubEndpointForHttpRequest('/accounts/sign_in', { error: 'invalid_grant' }, this.options);
visit('/accounts/sign_in');
fillIn('#email', this.account.email);
fillIn('#password', this.account.password);
click('input[type=submit]'); // blows up here when authenticate promise rejects
});
it('should not validate the session', function() {
andThen(() => {
expect(currentSession().isAuthenticated).to.equal(false);
expect(currentSession().get('content.secure')).not.to.include.keys(...Object.keys(this.resp));
});
});
}); Is there an alternative way of testing this user flow? |
Seems perfectly normal to me. How else would I test that I show an appropriate error message when I get a 422 response from some API? Also: this behavior is not just bad, it's also inconsistent. This doesn't break the test: new Ember.RSVP.Promise(function(resolve, reject) {
reject();
}); But this does: new Ember.RSVP.Promise(function(resolve, reject) {
reject("anything");
}); because in the first case |
Note that this is intimately related to ember-cli/rfcs#19, which proposes switching ember-cli's default AJAX adapter from It may be the case that the long-term path for Ember is don't use |
@jamesarosen I should have clarified. Unhandled Rejections with error objects are dubious to allow. Handle rejections or unhandled without a reason that is instanceof error. Are fine, and should already not be asserted |
Somehow I think you and I are heading towards the same conclusion, yet I don't quite understand what you said ;) Which of the following are OK in your mind? // reject with undefined, no catch:
new Ember.RSVP.Promise(function(resolve, reject) {
reject();
});
// reject with undefined, catch:
new Ember.RSVP.Promise(function(resolve, reject) {
reject();
}).catch(Ember.K);
// reject with string, no catch:
new Ember.RSVP.Promise(function(resolve, reject) {
reject("tsk tsk!");
});
// reject with string, catch:
new Ember.RSVP.Promise(function(resolve, reject) {
reject("tsk tsk!");
}).catch(Ember.K);
// reject with object, no catch:
new Ember.RSVP.Promise(function(resolve, reject) {
reject({ any: 'old', object: 'literal' });
});
// reject with object, catch:
new Ember.RSVP.Promise(function(resolve, reject) {
reject({ any: 'old', object: 'literal' });
}).catch(Ember.K);
// reject with Error, no catch:
new Ember.RSVP.Promise(function(resolve, reject) {
reject(new Error("tsk tsk!"));
});
// reject with Error, catch:
new Ember.RSVP.Promise(function(resolve, reject) {
reject(new Error("tsk tsk!"));
}).catch(Ember.K); |
All except for the unhandled rejection that rejects with the error. ( sorry on mobile or I would have cited the one) |
This I can get on board with :) |
@jamesarosen yup I thought so. And that is also the current behavior. It also handles more rejection scenarios. As long as they are handled in the same turn of the actions queue flush they will be good to go. I can likely expand that further yet, as we recently added some extra hooks to the run-loop. Anyways, I believe the current state is actually what one wants. If what I described does not happen, we must consider it a regression and fix . |
@marcoow I think you can work around it by |
Then why is the following code breaking? function failToDoX() {
return new Ember.RSVP.Promise(function(resolve, reject) {
Ember.run(null, reject, { an: 'error' });
});
}
test('it catches', function(assert) {
var caught = false;
failToDoX()
.catch(function(e) { caught = e; });
assert.ok(caught != null);
}); I get to the |
@jamesarosen I might be wrong but I think it depends on how
So the test above should work in isolation but it might not work when running whole test suite with acceptance/integration/unit tests. |
@twokul yeah, but that doesn't jibe with what @stefanpenner is saying:
and
What worries me is
That suggests to me that if something does its own run-loop-wrapping, I can't |
2 things: the run forces the flush early that being said, I'm not sure why a non-error object is causing the assertion to throw, that appears like a bug. |
these lines dont quite look correct https://github.com/emberjs/ember.js/blob/master/packages/ember-runtime/lib/ext/rsvp.js#L60-L67 |
Ultimately if something doesn't jive with what i said, it is very likely a bug/omission/regression |
this really isn't advised, be aware of the 👣 🔫 |
@stefanpenner: yeah, that might hide the problem but of course isn't actually a solution. IIRC, I ran into this: https://github.com/emberjs/ember.js/blob/master/packages/ember-runtime/lib/ext/rsvp.js#L61 and that actually makes the test fail regardless of whether the rejection is handled in the test or not. |
I have enumerated an added tests to the current behavior: https://github.com/emberjs/ember.js/pull/11903/files |
@marcoow https://github.com/emberjs/ember.js/blob/master/packages/ember-runtime/lib/ext/rsvp.js#L61 should be fixed with #11903 Turns out we were using a quite old version of RSVP and some more recent refactoring scenario solved the problem. This should correctly align it with my above statements, and also add the appropriate test cases. https://github.com/tildeio/rsvp.js/blob/master/lib/rsvp/promise.js#L177-L181 |
@stefanpenner great, looks good! |
All of a sudden I'm seeing this on Ember v1.13.11, which uses RSVP v3.0.14. The relevant code looks like function someMethod() {
if (someCondition) {
return RSVP.reject({ type: 'error', body: 'someCondition failed' });
}
} and the relevant test: test('when someCondition is false', function(assert) {
return theObject.someMethod()
.catch((error) => {
assert.equal(error.type, 'error', 'rejects with an error');
})
}); When I run the test in isolation, it passes. When I run the full test suite, I get
|
@jamesarosen that doesn't seem correct and the catch handler is attached sync. Can you provide a quick reproduction? |
Oh! Maybe it's coming from an earlier test. Like one I'm not attaching a |
@jamesarosen I would consider that test helper an anti-pattern, but the solution would be to simply handle the result of the callback before returning to run itself. So it is considered handled before the turn completes. |
I have no doubt you would. But the only other option is to wrap each test in If I seem frustrated, it's because I am. I appreciate your work and your help on this issue, but everything I suggest gets dismissed as "not what the core team wants you to do." I'm absolutely open to other ideas, though! If you have a suggestion on how to test -- at an integration or unit level -- that a method returns a rejected promise under certain conditions and has |
This often correlates to, don't cause yourself extra pain/frustration. The suggestions are not arbitrary.
can you provide a jsbin, or a demo app with the problem and i will gladly do so. |
Thanks, @stefanpenner. I'll keep digging and see what I can turn up. |
@stefanpenner pointed me in the right direction. test('foo', function(assert) {
return thing.signIn('username', 'bad password')
.catch((err) => {
assert.equal(err.body, 'Invalid username or password');
});
}): and it works just fine :) |
@jamesarosen we have been slowly massaging all these to continue to remove traps/pain points, its very possible your recall a previous state where this was not handled transparently. I suspect you will continue to see improvement in this domain. |
Given that ember-data v1.x calls |
I'm not here to debate the "right or wrong" of testing exceptions but I did want to offer a workaround for those like me who value testing 500 errors in acceptance tests (whether you are using ember-data or something else that wraps RSVP). Full credit for this goes to my good friend @williamsbdev who found the least hacky/but most functional way to get this working :) var application, originalLoggerError, originalTestAdapterException;
module('Acceptance to tdd error handling in your ember app', {
beforeEach() {
application = startApp();
originalLoggerError = Ember.Logger.error;
originalTestAdapterException = Ember.Test.adapter.exception;
Ember.Logger.error = function() {};
Ember.Test.adapter.exception = function() {};
},
afterEach() {
Ember.Logger.error = originalLoggerError;
Ember.Test.adapter.exception = originalTestAdapterException;
Ember.run(application, 'destroy');
}
});
test('some test that blows up when I try to tdd and a 500 makes RSVP reject', (assert) => {
server.post('/api/foo/1', (db, request) => {
//return a 500 with ember-cli-mirage/or whatever ajax stub library you choose
});
visit('/foo');
fillIn('#my-input', 999);
click('#my-btn');
andThen(() => {
//assert your modal shows up/or whatever biz thing happened
});
}); |
@toranb - Thank you for calling attention to this. I would like to give some background. One of the applications that we were working on had great validations before the request was made so it would not get a Ember.RSVP.on("error", function() {
// handle 401
// handle 403
// handle 5XX errors
// handle other errors
}); The problem then came when we wanted to test. This is why the monkey patching (done above in the comment by @toranb) on I completely understand what @stefanpenner was saying about handling the error by the end of the run loop. When running the tests, you don't know if the error has been handled and the test framework is going to let the user know that something is broken. This is great. We just took advantage of the |
refactor health service in order to make it possible to test within Ember's testing assumptions... see here for more details: emberjs/ember.js#11469
refactor health service in order to make it possible to test within Ember's testing assumptions... see here for more details: emberjs/ember.js#11469
add the health-service to check /health and start it in the app controller the health service had to be written in such a way as to make it possible to test within Ember's testing assumptions... see here for more details: emberjs/ember.js#11469
even if they are intended emberjs/ember.js#11469
FYI this is especially troublesome when attempting to use test('it really has come to this', function (assert) {
return run(() => {
return RSVP.then(() => {
assert.ok(false, 'expected promise to be rejected');
}).catch(error => {
assert.ok(true, 'expected promise to be rejected');
});
});
}); I challenge anyone who can show how to express a It is sad this is the state of affairs. If only there was a way to pause a run loop do you thing and the resume the run loop. |
Rejected promises give the qunit tests some issues, maybe as per emberjs/ember.js#11469 We can use the factoryGuy mockUpdate().fails() method to simulate it better anyways.
I think this post from EmberMap could be very helpful for people that end up in this conversation: https://embermap.com/notes/86-testing-mirage-errors-in-your-routes |
@dmuneras The article by @samselikoff describes my case precisely:
Unfortunately, the article only covers suppressing console logging. It does not address the original issue: preventing an acceptance test from failing when an Ember Data request rejects and is unhandled. We intercept the error with a decorator that shows a flash message to the user. But we re-throw the error, so that it's caught by an error reporter such as Sentry. The solution outlined above, overriding
Any ideas? PS Yes, I know that we could have our flash message decorator report to Sentry directly, so that we don't need to re-throw the error. But that's not its responsibility! And I don't want to sprinkle the app with Sentry reportings, instead of having it intercept all errors globally. |
@amk221 So you're overriding You're also doing |
Ember.onerror = ...
RSVP.on('error', ...); ...has been what the guides tell you to do for years. That, in combination with my previous link, gives you full control over errors in the app and test suite. I've never had any issues. Sorry I couldn't help! |
@amk221 That gives full control over reacting to errors. It does not stop QUnit from failing the test when there's an unhandled Ember Data network error. |
It does though? I have this top level error handling service in an addon and a test suite for it
|
@lolmaus I've just realised the reason this works for me, is because my AJAX requests are routed through RSVP (because I use Here is a test proving it is possible to test error states easily. It is something I wanted for many years as you can see from this old issue, which is pretty much a duplicate of what you want. |
I do this: debugger;
Ember.onerror = (error) => {
debugger;
}; I can see the first Anyway, I believe,
We use Ember Data, currently at version I'm a bit at a loss here. The previous solution of using |
Reported to |
Here's what I figured out:
Now I have control over errors in tests again, which I lost after Big thanks to @amk221 for taking time and effort to help me figure this out. |
Later to the party. I found something interesting, and think the reason it fails is that Qunit:
It's purely a testing thing that the errors are dispatched with runloop, even though you can catch in your test, qunit will pause your tests. Similarly, once this error happened
You can see above example will only fail the test cases, not block the rest of tests. But, the following example will pause the tests
The error is caused in like others might mentioned in previous comments:
Which does pretty much the latter case. So, what's one of the solutions? I don't know. I end up writting a catch statement in my business logic. See reference: Stackoverflow: Try... Catch in async await not catching error |
I'm writing tests for Ember Simple Auth and want to test the case that the authenticator rejects authentication. Therefor I stub its
authenticate
method to return a rejected promise:The problem now ist that this immediately causes
RSVP.onerrorDefault
to run which fails the test:Test.adapter.exception(error);
.I don't see a clean way to disable that behavior so that it allows this specific promise rejection. A working solution I came up with is
but that's obviously not sth. I want to be doing as it uses private API.
I think it would be great to have some way of using unhandled promise rejections in tests. While that would most likely not be of use for applications, I assume that more addons than just ESA might have a need for sth. like this.
The text was updated successfully, but these errors were encountered: