-
-
Notifications
You must be signed in to change notification settings - Fork 407
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
Grand Testing Unification #119
Conversation
I do think that |
|
Well put @mmun. I agree. |
|
||
```js | ||
import Ember from 'ember'; | ||
import { moduleForIntegration } from 'ember-qunit'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
moduleForComponent
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
moduleForIntegration
is correct. There is nothing specific to components about it. It can be used to test template helpers, or interactions between components. Think of it like a micro acceptance test.
On the other hand, unit testing a component can be done with moduleForUnit
, though I recommend sticking with integration tests unless you have a specific reason to unit test.
In short, we no longer need a moduleForComponent
and keeping it (other than for backwards compatibility) would cause confusion.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I thought this is an existing test using the old way. In any case, it doesn't match with moduleForComponent
below on line 390, which I think it was meant to
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You're totally right. My mistake!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ya, overeager find/replace. Fixing....
👏 My only thought/concern is around https://github.com/rwjblue/rfcs/blob/42/text/0000-grand-testing-unification.md#registering-custom-waiters I've seen apps where waiters are registered in code (though it never feels right, but often does seems to be the easiest way to get at the conditions that matter for waiting), and the tests are unaware of the custom waiters. |
For some references to code in the wild, I'm using the following code for testing: |
@kategengler this RFC for adding something like |
Yes, I have seen this as well, but I generally feel that modifying your app code to I could easily see adding a mechanism for our test setup code to invoke a method on each addon so that the addon could do some custom setup. This would allow them to register custom helpers, waiters, and QUnit assertions. However, I also really like knowing where these things are coming from by registering them in the shared I will add an "unanswered question" for this, so we do not lose track of this concern. |
Wow. I just have to say, this is going to mean a lot of upgrading. First strongly consider (and add to alternatives) adding async helpers to unit/integration tests but not removing anything. I would strongly suggest:
|
@stefanpenner While that may be true as far as enabling these features goes, I don't see how this is possible (especially without heavy use of reopen) without modifying ember, ember-qunit, and ember-mocha. ember-cli may not require heavy modification if ember is addonized first. |
the current model is a pretty big hazard, but should be supported until 3.0. Transitioning new apps and users away seems prudent. Post 3.0 we can swap, extracting the current to an add-on etc. You raise a good point though (i think), and that is we should ensure it is possible for both to run in the same code-base at the same time, to ease upgrades. |
This work is basically done (pending using @trabus's new test infrustrature + rebase), we can safely assume it will land before. |
@rwjblue Somewhere for addons to hook in would alleviate part of my concern. I agree that modifying app code to incorporate @davewasmer Thanks for the pointer to the RFC, I do think there are uses for |
One example is PouchDB-backed Ember applications, which can’t rely on monitoring Ajax calls to know that operations are complete. ember-cli-test-model-waiter has helped me in that sense, but I still have weird intermittent test failures that are probably related to not having proper ordering. I have in the past used a custom helper that waits on a promise set in a controller but it makes me feel dirty 😁 |
Nothing is proposed to be removed in this RFC. As stated in the RFC, the plan is to implement everything as new additive API that can be consumed and does not propose changing any existing API's.
The changes here are not related to Ember version, and will not live in the Ember repo.
As I state above and in the RFC, these changes will be additive. When this RFC is approved/merged we will begin rolling out the new API's proposed here.
The reason this will be implemented in a separate library instead of Ember itself is so that we can implement exactly this sort of cross version support. As mentioned in the unanswered questions section, the lowest Ember version that would be supported has not been determined, but I would prefer 1.12.
I will await a decision from #115 before embedding those changes here, however it should be very straightforward to implement whatever solution is decided there.
As stated in the "Migration Plan" section of this RFC, the deprecation of the existing testing infrastructure will be completely based on feedback of the new system. If things are going well, we could start deprecating existing features within a minor version or two of when these changes land. I will update that section to put a clearer timeline there.
Indeed. |
All existing API's will remain unchanged and will be able to run alongside and independent of the new proposed API's. It will absolutely take time to upgrade (even if my hopes of some automation assistance come to fruition), and we need to ensure that existing test suites continue to work. |
Not true. From the RFC
I suggested, as an alternative, only adding unit/integration async test helpers, not unifying them, and not deprecating any of the above mentioned APIs.
My understanding from your and @stefanpenner 's previous statements is that all of the blueprints are planned to be moved from the ember-cli repo to the ember repo as part of addonizing the ember repo, so that they can be correct according to the ember version being used. |
Nowhere in the quoted section do I speak of "removing" anything. I speak of deprecating once we are generally happy with the new solution.
I will add that to the alternatives section. However, continuing down the path of wall papering over large problems in our infrastructure seems very bad to me. |
This waiter could be written as such (outside of app code): // addon-test-support/waiters/running-transitions.js
import { testWaiter } from 'ember-test-helpers';
export default testWaiter(function() {
let transitionMap = this.owner.lookup('service:transition-map');
return transitionMap.runningTransitions() === 0;
}); This is roughly the same implementation that exists in liquid-fire today, but as of this moment it would still require manual registration (in the shared |
@rwjblue I think its a scenario with two gross options -- do you let your app know about your testing framework or do you let your acceptance tests know about the internals of your app? If that way (looking up + reaching in) is generally accepted as kosher and with a hook for addons, I think my concerns are allayed. |
Yeah, I agree. In this case (liquid-fire), I believe that the test waiter I wrote is just another consumer of a normal public API (the Regardless, I think we are on the same page. I will try to come up with a nice API for the hook mechanism... |
Been discussing with @rwjblue but wanted to share a potential tweak to test helper semantics that I think will solve a lot of tricky issues pertaining to route loading substates and other intermediate/transient states that occur while test waiters are still unsettled. Let's say you have the following: await this.click('.some-btn');
await this.click('.another-btn');
let $sel = await find('.banner');
assert.equal($sel.text(), "wat"); I don't think the semantics of the above are 100% spelled in the RFC, but most would assume the behavior is: 1) try clicking In all cases, if the selector (e.g. These are the somewhat classic semantics that most people have come to expect from ember-testing, but with these semantics, it's still really hard to test:
There is a subtle tweak that has been proposed that (I believe) maintains backwards compatibility and should for the most part remain in line with people's mental models. Given the same code above:
I'd call this the "retry-until-settled" semantics. If this seems weird/unusual, keep in mind that if you're structuring tests using the async fn approach proposed in the RFC, it doesn't actually change anything about the behavior of the test, aside from the following benefits: 1. Loading substates are now testableFor instance if you use loading substates in your app, and your loading template has await this.click('a.some-link-to-slow-route');
let $loadingBanner = await find('.loading-banner');
assert.equal($loadingBanner.text(), "Loading...");
let $slowRouteBanner = await find('.slow-route-banner');
// this will resume when the slow route finishes loading Previously, there was no easy way to test loading substates because ember-testing wouldn't run your 2. Tests are more immune to timersSince ember-testing's internal waiters block on unfired With "retry-until-settled", you can test features even if some background outstanding timer hasn't yet fired. NOTE: this is only a partial solution to the trickiness surrounding testing timers. This approach does make things easier / faster when tests pass, but it also means that if a selector fails to match, the test will only fail when test waiters settle (or the test case times out). In other words, there's still room for testing abstractions brought to you by
|
Can "retry-until-settled" be made reliable for testing loading routes? It leaves me feeling a little nervous that the runtime might not "catch" the loader before the model resolves. |
@courajs I don't know what the convention is, or whether there is even one, but I would think most people testing something timing-dependent things like loading routes are already using testing tools to control the resolution timing of model hook promises (perhaps ember-cli-mirage does this?). Something like the following: let resolveModel = stubModelHooks();
await this.click('a.some-link-to-slow-route');
let $loadingBanner = await find('.loading-banner');
assert.equal($loadingBanner.text(), "Loading...");
resolveModel({ name: "fake model" });
let $slowRouteBanner = await find('.slow-route-banner'); This pattern seems pretty robust against timing dependencies (or at least reduces them to the same robustness as code that resumes after test waiters settle). That said I do think we might want to consider using something like Mutation Observers to "kick" a paused retrying finder so that by default Ember is catching as many intermediate states as possible, but generally speaking I think a stubbing/mocking controller model resolution solution makes the most sense here. |
@machty iirc Capybara uses something much like retry-until-settled and it seems to work well. Perhaps worth discussing: they ended up adding helpers of the form |
@jgwhite FWIW I've been spiking out some of the APIs for this RFC in ember-concurrency land here This is the API I had in mind for what you're describing: Basically, RE Capybara: they only have that awkward unfortunate distinction for their basic vanilla set of assertions, but the problem goes away when using RSpec. In our case, since all Also, to be clear, these insights were inspired by my experiences with Capybara, but the big difference between what I'm proposing and Capybara is that unlike Capybara, we have an established notion of settledness that we leverage to make our assertions/finders fail ASAP. Specifically, the default behavior of the |
@machty |
Would await this.click('.delete-all-users');
await this.click('a.nav-to-user-list');
await this.find('.exists-on-user-list');
await this.find('.user', { count: 0 }); |
@courajs I think you've identified the fundamental tradeoff with this API, in that there are definitely still some awkward cases where you do want the settling behavior. I think in this case you have two options:
Option 2 isn't unique to what I'm proposing; there's been times in the past with classic test waiter behavior where I Maybe there's a way we can improve this testing story even further; I just don't see any alternative test-waiter-y API that doesn't suffer from the major issues of making intermediate states untestable (same goes w WebSockets).
I might be misunderstanding you but I actually like that within Either way, I think it might be a good idea to publish an addon that let's people try out some of these ideas and see how they actually feel in production apps. |
I have one grief already present in current test system and which will amplify with this new proposal. The fact that we will rely on test('should be ok', ({ assert, context, helpers }) => {
context.owner.lookup();
context.get();
helpers.find();
assert.ok();
}); |
@tchak - That is basically a qunit/mocha issue. I know on the QUnit side @martndemus opened an issue a while back about this (I believe the suggestion was to pass the context as a second argument or something?). |
@rwjblue I kinda like my idea with hash destructing. But as I said, this is mostly an aesthetic issue. It will be very easy to come up with an addon to make it work the way I want it. So not a big deal. |
I have a proposal that isn't exactly related to all of this, but might end up being, so I want to mention it here. One small thorn in my side that has come up a few times is the fact that AFAIK
I usually opt for number 2: moduleFor('router:main', 'my mixin or whatever', function (assert) {
}); It doesn't look like it would be that difficult to factor the subject logic out of class AbstractTestModule {
// as-is today
}
class IsolatedContainerTestModule extends AbstractTestModule {
// All the code from TestModule except subject-related code
}
class SubjectTestModule extends IsolatedContainerTestModule {
// identical to TestModule's current functionality
}
class IntegrationOrAcceptanceOrWhateverModule extends SubjectTestModule {
// as-is today
} which would allow, for example, a nice way of testing mixins that call into a service via import Ember from 'ember';
import { containerModule } from 'ember-qunit';
import MyCoolMixin from 'my-addon/mixins/cool';
containerModule('my cool mixin', function() {
needs: [
'service:something-or-other'
]
});
test('it works without a someFunction() override', function(assert) {
this.register('object:test', Ember.Object.extend(MyCoolMixin));
let obj = this.container.lookup('object:test');
Ember.run(() => obj.doSomething());
let service = this.container.lookup('service:something-or-other')
assert.equal(service.get('callCount'), 1, 'it called the service');
});
test('it works with a someFunction() override', function(assert) {
this.register('object:test', Ember.Object.extend(MyCoolMixin, {
someFunction() {
return;
}
}));
let obj = this.container.lookup('object:test');
Ember.run(() => obj.doSomething());
let service = this.container.lookup('service:something-or-other')
assert.equal(service.get('callCount'), 1, 'it called the service');
}); That may seem like a long way to go just to avoid including I've though about working on a PR for this functionality, but that seems silly with the test unification on the horizon, so I wanted to bring this up here to get people's thoughts on it, and see if we want to factor it into the work described here, or if I should just go file an issue in |
Hmm, I believe it does not attempt to lookup a registered object if you specify |
@rwjblue I'm not following. If I use moduleFor, I get a TestModule, which sets this.subjectName to be the first argument, and treats Am I really confused here? I'm happy to go file an issue in ember-test-helpers, but it sounds like you're talking about it like it's a bug, and it looks like it was never intended to work that way... |
# Unresolved questions | ||
|
||
- Should we prefer `getOwner(this)` to `this.owner`? I believe that accessing the owner in testing is much more common (for mocking/stubbing, looking up singleton objects, etc), so we should use `this.owner`. | ||
- Should `this.find` return a jQuery wrapped element? I would prefer to stick with the main DOM API's here, so that we have a chance to share tests between the Fastboot and Browser. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@rwjblue regarding…
Should
this.find
return a jQuery wrapped element? I would prefer to stick with the main DOM API's here, so that we have a chance to share tests between the Fastboot and Browser.
We worked on https://github.com/cibernox/ember-native-dom-helpers/releases/tag/v0.2.0 cc\ @cibernox and decided to only return HTMLElement
or NodeList
from find
helper. If a test must use jQuery the developer can use jQuery(find('a'))
explicitly.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@rwjblue I believe that us should to use DOM API NATIVE. I like JQuery, but the Ember needn't it internal. I agree with @pixelhandler, if the developer want to use JQuery him can to use.
Being the Grand Testing Unification, don't we need a story for testing node.js code in Ember CLI addons? Or would that be outside the scope of this RFC? |
I'm not sure if anyone has done this already, but I couldn't find it so I made an ember-watson transform: abuiles/ember-watson/pull/112 |
Awesome @dwickern :) |
Is this still supposed to be open? |
I think this was superceded by RFC #232 . |
Not fully actually. I'll close for now, but there are still ideas that need to be moved forward. |
Rendered