-
Notifications
You must be signed in to change notification settings - Fork 21.9k
Deprecate assigns() and assert_template in controller testing #18950
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
Comments
Just sent a PR with deprecation warnings. I tried to follow the style in other actionpack warnings. |
I agree testing what instance variables are set is a bad idea, but i'm not sure to test what mutations happened to the DB, because this is not innards of the controller too. |
That’s more a matter of whether you’re testing the controller or doing a full system test. I’m very sympathetic to system testing, but the black box can also appear in shades of grey for greater efficiency or other concerns.
|
Ok, but if you check mutations happened in DB, are you overstepping controllers tests? |
I don’t think so. Controllers don’t own the model. They do own their ivars, though. So I see no issue there.
|
When testing a unit, you want to see the result of calling a function. That result comprises a) return values and b) external state modifications. In nearly any other case, internal (ivar) modification should not be tested, but this is not true with controllers. Why? Because the next high-level step of the render chain is to copy the controller's ivars* to the view. That's an external—albeit delayed—state change, and the fact that it occurs with ivars is merely an implementation detail/convenience. If there was an accepted, default way to pass information to views without using ivars, then I'd agree with removing assigns(). Until such a mechanism is "The Rails Way" (yes, one can do it already with mechanisms like decent_exposure, but the guides do not encourage that), assigns is crucial, especially when trying to fix some "bad" controller code.
|
This is an honest question. With the removal of these two checks, what is the benefit of continuing to use a controller test over an integration test that tests a single controller? |
@cupakromer performance. ATM controller tests are still a bit faster then integration tests. We would like those to have the same performance, so we can only have one. |
i think that @listrophy has some valid points there. in case i actually want to test the way a controller accesses views or sets instance variables (because maybe i use it as some kind of internal API), what would be the recommended way? |
I agree with @listrophy and @phoet. It should be possible to test what messages are passing to external interface (messages - instance variables, external interface - views/templates). |
You can of course do whatever you want. Ruby is open. But Rails does not have to encourage this behavior, and won't, starting from the next version. |
@dhh how, specifically, do you think we should test the interface between controllers and views? Strictly integration testing? just trying to get clarification here. |
Since this is for 5.0, could we remove and also deprecate for the remainder of the 4.X lifecycle? |
@listrophy I think that's the point. You shouldn't test that interface as their being separate is an implementation detail of your framework, not something you should care about. |
If you want to continue to test ivars, you can just do #instance_variable_get. And yes, this is a change for Rails 5.0. The change may well be that we’re dropping controller tests entirely in favor of integration tests, now that the latter is faster on rails/master than the former was on 4.2.
|
Honestly, I don't actually want to test ivars. I (sometimes) want to test what data is getting sent from controllers to views. It just so happens that the main way to do that right now is through ivars. And to be completely fair, I usually don't write controller unit tests. I still think it's important to not shut that door in cases where it's useful, such as all those brownfield projects out there. If such functionality were pulled into a gem rather than just removed, you wouldn't hear me complain. |
I agree with @listrophy, I'd rather not test ivars. As has been pointed out, it is necessary when attempting to isolate the controller's contract with the view. Honestly, for me, what the controller passes into the view and what the view decides to do, or not do, with that information are fairly separate concerns. As long as the base contract is met. These two parts of Rails have always been tightly coupled, I'm guessing due to ERB's behavior for how it evals on an object. I have recently notice Rails even takes extra steps to prevent certain ivars from crossing this "invisible" barrier. If the future route is to drop controller testing in favor of integration testing you won't hear me complain. Nor if this behavior is extract into a gem as suggested above. |
iVars essentially serve as the recommended "API" between controllers and views. It's how controllers pass data to views, as instructed and given by examples in almost all the Rails docs, it seems to be the way Rails wants you to do it. So I'd disagree that controllers "own" their iVars, in Rails design the iVars serve as the interface between controllers and views. I don't see any good way to 'unit' test a controller without testing iVars as well as templates rendered. The output of a controller is essentially just those, iVars and templates rendered. If you only test HTTP status codes and HTML returned, as output, I think you're basically doing a "full system test," you might as well be doing an integration test. Which, part of this thread suggests that's what Rails means to encourage, do not unit test controllers, only do integration tests. Myself, I find unit testing controllers to be useful -- most especially when delivering a controller (or controller mix-in) in an engine. When delivered via an engine (eg like devise), we want to test inputs and outputs for a contract the engine provides very carefully, make sure they are exactly as specified. It would be difficult for me if they were to go away. But if you mean to promote "only integration tests for rails", I guess that's consistent, that's basically what happens when you can't test iVars or templates rendered anymore, whether you use the Rails integration test base class or the functional/controller test base class, you're basically doing an integration test now. It will be easy to work around the missing assigns with |
How should I test my controllers in isolation of my views? Is that not the advantage of the MVC model? This feels like a very heavy handed way of strong arming people into writing integration tests rather than unit tests. I appreciate that Rails is "convention over configuration", and perhaps the convention should be to to write integration tests but I should be able to choose to unit test controllers, just as easily. I know I could probably reimplement these myself if I so wished such is the nature of ruby, but it feels a bit like fighting against the framework. I can't help but feel that there are better ways to point people towards integration tests without removing these helpers. |
One of the foundations of object-oriented programming is the idea that encapsulated objects pass messages between each other to produce systemic behavior. When I'm testing that those objects are correctly implemented, I want to know that the messages they are sending to collaborators are happening at the right time, and in the right way. For better or worse, Rails controllers send messages to templates/views via ivars (instead of method calls). Therefore, I need to test that ivars are being set. I can certainly do that with things like #instance_variable_get, but I would prefer that #assigns and #assert_template stay in place. |
Since iVars are considered a public interface for the views, and nobody really likes that we have to access what is considered private variables from outside the class, would it make sense to instead change the access of those iVars to be delivered from a public # posts_controller.rb
def index
assign :posts, Post.published
end # index.html.erb
<% posts.each do |post| %>
<%= render post %>
<% end %>
<!-- alternatively -->
<% assigns(:posts).each do |post| %>
<%= render post %>
<% end %> This way, we get a nice interface for the views to get things in the controller without accessing it's iVars, we can get an |
For those missing The Rails team has heard your points, but we'll go ahead with the deprecations. Kasper
|
The assert_select approach lets you test the controler+view as an integrated unit. Those of us unhappy with that want to test the controller as a unit isolated from the view. This is particularly useful/important when delivering an engine, where the controller (or mix-in to local controller) might be used with a locally overridden view -- you want the controller, as an isolated unit separated from the view, to have a contractual interface, so the local app can provide a view depending on that interface. Devise would be one example. But okay, it's clear that Rails team does not see the value in testing controllers as an isolated unit separate from views, but thinks it's preferable to do systems or integration tests instead, or controller+view 'functional' tests. Hopefully there's enough people who disagree that there will be capacity to create a third party plugin that restore the ability to test controller as an isolated unit, and maintain it across versions of rails that may otherwise break it. We will see. |
I'd be happy to see a plugin that provides this testing approach. Just because it's not in core doesn't mean you can't use it. If someone wants to do the work, we can update the deprecation notice to point to this plugin.
|
Totally disagree with this philosophy like others above. Controller variables are not internals of the controller, they are the interface between the controller and the view and should be rigorously tested. Integration tests do not allow you to test specifically enough. Simple example: a view formats a date <%= @startdate.to_s(:long) %> Is it not reasonable to test the controller to ensure that @StartDate is never nil? Furthermore, please do not change the method of controller to view interface in order to justify this deprecation. Setting an ivar in the controller and using it in the view is one of the beautiful RoR conventions that enable great productivity imho. Poll the community and you will find everyone using the gem and moving forward into Rails 5 with controller tests that use assigns(...). |
@vanboom Why can't you test that
And if you polled the community there are many that prefer the new way, myself included. It works great. |
- Remove `assert_template`. See rails/rails#18950 - Assert presence or contents of essential attributes or fields
Now that we know about controller specs, we are going to use them to test invalid forms instead of a feature spec. This follows the guideline: > Use feature specs for the happy path and controller specs for the sad > path. However, controller testing is discouraged now and that's why `rails-controller-testing` gem has been created. See the following issue for details: rails/rails#18950 In addition, there are some options for checking the sad path without using feature specs, which are expensive. Read section `Controller Specs` in `Testing Rails` book for ideas.
why? restore old controller/view spec functionality see https://github.com/rails/rails-controller-testing see rails/rails#18950
why? restore old controller/view spec functionality see https://github.com/rails/rails-controller-testing see rails/rails#18950
why? restore old controller/view spec functionality see https://github.com/rails/rails-controller-testing see rails/rails#18950
why? restore old controller/view spec functionality see https://github.com/rails/rails-controller-testing see rails/rails#18950
Here's a little snippet that was handy for me: private def assigns(ivar)
controller.instance_variable_get(:"@#{ivar}")
end |
- Uses `assert_select` instead of `assert_template`. See rails/rails#18950 (comment) - Adds assertion exposing bug on Countries and Other Localities page - Removes unneeded`include`
- Uses one assertion per method - Uses `assert_select` instead of `assert_template`. See rails/rails#18950 (comment)
…jects#show in respect to published items & projects [REF] Renamed `log_in_user` to `sign_in` which corresponds to the ActionController::TestCase naming Converting ForumsControllerTest into an integration test was needed since the views do some additional collection logic that wasn't included in the `@items` variable. This should be done anyway since * We're moving to become an API where we need integration tests * Controller tests are (basically) being deprecated in 5 rails/rails#18950 (comment) Ported the ProjectsControllerTest along with it
…jects#show in respect to published items & projects [REF] Renamed `log_in_user` to `sign_in` which corresponds to the ActionController::TestCase naming Converting ForumsControllerTest into an integration test was needed since the views do some additional collection logic that wasn't included in the `@items` variable. This should be done anyway since * We're moving to become an API where we need integration tests * Controller tests are (basically) being deprecated in 5 rails/rails#18950 (comment) Ported the ProjectsControllerTest along with it
Testing what instance variables are set by your controller is a bad idea. That's grossly overstepping the boundaries of what the test should know about. You can test what cookies are set, what HTTP code is returned, how the view looks, or what mutations happened to the DB, but testing the innards of the controller is just not a good idea.
The same goes for assert_template. This ties the test to the internal structure of how your views are organized. That's not what the test should be testing. It should be testing what DOM elements it expect to be there. Rearranging your templates and partials should not cause these tests to break. Deprecate and recommend using assert_select instead.
The text was updated successfully, but these errors were encountered: