Registry / Container reform #46
Conversation
`isolatedContainer` for unit testing. A mechanism will be developed to specify | ||
which initializers should be engaged in the initialization of this instance. | ||
In this way, we can avoid duplication of registration logic, as is currently | ||
done in a most un-DRY manner in the [isolatedContainer](https://github.com/switchfly/ember-test-helpers/blob/master/lib/ember-test-helpers/isolated-container.js#L56-L79). |
mixonic
Apr 9, 2015
Member
For Ember-Data we use DS._setupContainer
:
If Ember only needs to register/inject, then I would prefer such a hook since they can be easily layered onto a single registry in an arbitrary order.
However I note that you suggest the name be isolatedApplicationInstance
- does this imply you will return a real instance and not simply a container with a configured registry?
For Ember-Data we use DS._setupContainer
:
If Ember only needs to register/inject, then I would prefer such a hook since they can be easily layered onto a single registry in an arbitrary order.
However I note that you suggest the name be isolatedApplicationInstance
- does this imply you will return a real instance and not simply a container with a configured registry?
dgeb
Apr 9, 2015
Author
Member
Yes. If Container
is private and only accessible through an ApplicationInstance
, then I believe that's the level at which it should be available for testing.
Yes. If Container
is private and only accessible through an ApplicationInstance
, then I believe that's the level at which it should be available for testing.
With the recent container / registry split we were burned pretty badly with our test helpers. We had created quite a few helpers to do proper testing setup/mocking/teardown/etc. I personally am a little weary of the idea of isolating these given that interaction with the container had previously been encouraged for testing and other initializers. I guess I have no real concrete reason as to this shouldn't be done, other than I don't really want to refactor all my test helpers again. |
Big, big
I can't speak for everyone, of course, but @wycats and I have been very openly against anyone treating the container API as public. There are cases where you have to use it, but there is a reason it is marked private. This proposal allows us to (finally) provide an API we can encourage people to use. |
@tomdale Certainly my primary use case is around testing. Using containers to initialize and mock stuff. But also, it is one of the arguments provided when creating an initializer. And for a period of time, before services people were encourage to used injection this way. This is one of dozens examples that come up when you Google ember dependency injection. http://ember.zone/ember-application-initializers/ Any ways, just to make my position clear, I'm not against it. Just pointing out it might be a source of pain for some folks. |
@workmanw Point taken. With Dan's RFC, assuming you were indeed sticking to initializers and coloring within the lines, the refactor is extremely mechanical: just update all of your initializers and replace |
|
Seems reasonable. Perhaps |
@tomdale Thanks for your support of this proposal. @workmanw I'm sorry that the churn in this area has already affected you. I initiated much of this churn with the Registry / Container split as part of the SSR refactor. That was a necessary precondition to the Application / ApplicationInstance split that @tomdale and @wycats performed to enable SSR. Only now that their work is nearing completion is it possible to come back to rethink Registry and Container access. I hope the end result of all this work is a clean set of interfaces that shield developers from any complexity introduced by these refactors. And since these interfaces will finally be thoroughly public, we can avoid similar churn and breakage in the future. |
|
||
# Alternatives | ||
|
||
The obvious alternative is to make `Container` and `Registry` fully public |
stefanpenner
Apr 12, 2015
Member
most of the in-app usages of container are the result of not having:
- class level injection rules (not global injection rules, which don't inherit)
- factory injections are backwards, instances should get references to factories and not the other way around.
beyond that, there exist dynamic situations where has/lookupFactory
should be exposed, but lookup
should never be exposed as this should have been the result of an injection (lazy or constructor).
most of the in-app usages of container are the result of not having:
- class level injection rules (not global injection rules, which don't inherit)
- factory injections are backwards, instances should get references to factories and not the other way around.
beyond that, there exist dynamic situations where has/lookupFactory
should be exposed, but lookup
should never be exposed as this should have been the result of an injection (lazy or constructor).
* Are the public methods listed above sufficient or should any others be | ||
exposed? | ||
|
||
* What mechanism should be used to engage initializers in unit and |
stefanpenner
Apr 12, 2015
Member
Unit don't get initializers. By definition this is then no-longer a unit test. Unit level tests can get collaborates provided as constructor Args, but should have no container awareness. My above comment should help remedy most use-cases where users app code violated this.
Unit don't get initializers. By definition this is then no-longer a unit test. Unit level tests can get collaborates provided as constructor Args, but should have no container awareness. My above comment should help remedy most use-cases where users app code violated this.
dgeb
Apr 13, 2015
Author
Member
All of Ember's "unit" tests rely on the concept of a container that is initialized. The isolatedContainer is not truly "isolated", but relies on registrations performed by Ember and even Ember Data. I'm sure we can all agree that the code in isolatedContainer
is un-DRY and a temporary hack, so let's solve this problem more elegantly.
As the discussion about integration vs. unit testing components makes clear, we need a broader discussion about dependency injection, what's exposed to tests, and ember-cli's conventions regarding integration and unit tests. This goes beyond the scope of this RFC, but definitely overlaps it.
@stefanpenner I'll ping you soon to try to set up that overdue hangout / discussion.
All of Ember's "unit" tests rely on the concept of a container that is initialized. The isolatedContainer is not truly "isolated", but relies on registrations performed by Ember and even Ember Data. I'm sure we can all agree that the code in isolatedContainer
is un-DRY and a temporary hack, so let's solve this problem more elegantly.
As the discussion about integration vs. unit testing components makes clear, we need a broader discussion about dependency injection, what's exposed to tests, and ember-cli's conventions regarding integration and unit tests. This goes beyond the scope of this RFC, but definitely overlaps it.
@stefanpenner I'll ping you soon to try to set up that overdue hangout / discussion.
I'm not sure if this is something that fits the scope of this RFC, if that's the case just ignore me. Lately I've been digging into testing story for Ember.js and one thing bothers me about testing an application with services. At the moment there's no easy way to inject mocked services during acceptance testing, or at least there's no way that I know of. It would be great if new public API allowed something like this:
This would make acceptance tests use a mocked service. There are multiple ways to handle such behaviour, but all that I know of require preparing code for substitution (like creating adapters in ember-data). |
Ideally we will have a way to register a replacement service as described above, using a public API. In testing comma components this has proven very necessary to isolate issues in the front end vs. the back end code. |
That was comms components. |
@drogus I am also very interested in the acceptance testing story. I think there are at least three blocking issues:
|
Yeah, the only workaround I found to overcome this issues is to reopen resolver: application.registry.resolver.__resolver__.reopen({
resolve: function(fullName) {
if(fullName === 'service:storage') {
return mockService;
} else {
return this._super.apply(this, arguments);
}
}
}); which is clearly not very elegant and will break with any bigger internal changes. |
Here's one way to solve the acceptance testing story:
In this way, registrations will be resolved in order by:
In this way, mocks can be registered with the application instance and those registrations will take precedent when resolved. |
internally maintained container: | ||
|
||
* `lookup` | ||
* `lookupFactory` |
bf4
Jun 12, 2015
Great, so code such as in ember-islands that finds App.DocPrinterComponent
would change as below?
-var container = App.__container__;
-var componentLookup = container.lookup('component-lookup:main');
-var component = componentLookup.lookupFactory('doc-printer', container);
+var component = App.lookupFactory('doc-printer');
Great, so code such as in ember-islands that finds App.DocPrinterComponent
would change as below?
-var container = App.__container__;
-var componentLookup = container.lookup('component-lookup:main');
-var component = componentLookup.lookupFactory('doc-printer', container);
+var component = App.lookupFactory('doc-printer');
bf4
Jun 16, 2015
Or would it be like
App.instanceInitializer({
initialize: function(instance) {
instance.container.lookupFactory('doc-printer');
}
});
(h/t al3xnull in slack https://embercommunity.slack.com/archives/needhelp/p1434479693008755 )
Or would it be like
App.instanceInitializer({
initialize: function(instance) {
instance.container.lookupFactory('doc-printer');
}
});
(h/t al3xnull in slack https://embercommunity.slack.com/archives/needhelp/p1434479693008755 )
dgeb
Jun 17, 2015
Author
Member
Sorry to miss your earlier question. So the lookup
and lookupFactory
methods would be exposed directly on the instance, in the same way as register
and inject
:
App.instanceInitializer({
initialize: function(instance) {
instance.lookupFactory('doc-printer');
}
});
(Note: @stefanpenner has concerns about lookupFactory
as it's currently implemented. It may be replaced entirely for 2.0)
Sorry to miss your earlier question. So the lookup
and lookupFactory
methods would be exposed directly on the instance, in the same way as register
and inject
:
App.instanceInitializer({
initialize: function(instance) {
instance.lookupFactory('doc-printer');
}
});
(Note: @stefanpenner has concerns about lookupFactory
as it's currently implemented. It may be replaced entirely for 2.0)
bf4
Jun 18, 2015
alrighty, thanks
alrighty, thanks
@dgeb that means that there is parallel |
@igorT there is no (non-deprecated) There are two registries now: one on the application instance and one on the application. Each has a When resolving, the instance's registry "falls back" to the application's registry if it can't resolve. If a resolver is associated with either registry, then it will be consulted before the custom registrations in that registry. The solution here (which I'm about to PR) is to skip setting the resolver on the instance's registry and only set it on the application's registry. This allows custom registrations made through the instance's registry to take precedence over both the resolver and registrations made on the application's registry. |
My bad I misread |
Implemented and merged via emberjs/ember.js#11440 Should this RFC just be closed now? |
Yes :-) The filename should be changed to |
Thanks @mixonic! It's ready for review now. |
RFC