How to stub a flash in a RSpec spec? - ruby-on-rails

In my view, I have a hidden_field_tag whose value is a flash set in the controller. In other words, the flow is as follows:
Controller:
def home
flash[:id] = 123
end
View:
<% form_tag(new_invitee_path) %>
<%= hidden_field_tag :referer, flash[:id] %>
<% end %>
Params submitted to new_invitee_path:
{ "referer" => "123" }
I can confirm that in manual testing this works appropriately, but I can't figure out how to stub appropriately.
In my test I have:
before do
#set flash
visit '/home'
fill_in "rest_of_form"
click_button "submit_form
end
Where below are the things I've tried to do for set flash and the error messages I get:
flash[:id] = 123
# OR
flash.now[:id] = 123
# both render error: undefined local variable or method `flash' for #<RSpec::Core::ExampleGroup::Nested_1::Nested_1:0x007fc1040f7d60>
# Have also tried a tactic found online to set flash for response object like this:
visit '/home'
response.flash[:id] = 123
# OR
response.flash.now[:id] = 123
# both render error: undefined local variable or method `response' for #<RSpec::Core::ExampleGroup::Nested_1::Nested_1:0x007fe118a38490>
#Have read online that it's a problem with the flash being sweeped, so I tried to stub out the sweep, but am unclear how to set the anonymous controller or whatever correctly
controller.instance_eval{flash.stub!(:sweep)}
flash[:id] = 123
# OR
flash.now[:id] = 123
# renders error: undefined local variable or method `flash' for nil:NilClass

Your spec is a feature spec, so the spec environment has no access to things like the flash. Don't try to work with the flash directly. Instead, ideally, test that the user's view of the app looks and/or behaves the way that it should if the flash value is set the way that it should be. I wouldn't just test that the hidden field is present in the form; I'd test that it has the effect that it should after the form is submitted. That's what feature specs are all about: testing that the application works as a whole from the user point of view.
If the flash value isn't ever used in the UI, just logged or stored in the database, it would be OK to test that the log line or model object has the value that's stored in the flash. (The user here is the admin who would look at the log or whatever later.) But if the flash does affect the UI, testing that is preferable.

This seems to work pretty well for me:
YourController.any_instance.stub(:flash) { {some: "thing" }}

Related

Unpermitted parameter error when adding request parameter while using Administrate

I'm using Administrate v0.11.0 with search_term textbox,
it works totally fine,
and now I want to add a request parameter my_search_condition_flag which is a boolean flag value that affects search condition.
In my index action of controller,
I added the following line, so that requests with this parameter pass the Strong Parameters validation.
params.permit(:search, :my_search_condition_flag)
The rest of the code in index action is simply copied from ApplicationController.rb of Administrate.
When I make a HTTP request with request parameter my_search_condition_flag=1 ,
my index action is processed just fine,
but HTTP response returns following error:
ActionController::UnpermittedParameters in Admin::MyPage#index
Showing /usr/local/bundle/gems/administrate-0.11.0/app/views/administrate/application/_search.html.erb where line #19 raised:
found unpermitted parameter: :my_search_condition_flag
which is raised from rendering method of search_term textbox inside index.html.erb
<% if show_search_bar %>
<%= render(
"search",
search_term: search_term,
resource_name: display_resource_name(page.resource_name)
) %>
<% end %>
I've already tried the following to my Dashboard class, introduced here:
# -- Overwrite the method to add one more to the permit list
def permitted_attributes
super + [:my_search_condition_flag] # -- Adding our now removed field to thepermitted list
end
How can I tell Administrate to permit a parameter which I want to add?
Do I have to use request body instead? (which I don't want)
You were on the right track there. The exception originates at /app/views/administrate/application/_search.html.erb:19, as you mention. If you look there, you'll see it uses the method clear_search_params, which also uses strong_parameters to allow/deny query params. You can override this with a helper of your own. For example:
module Admin
module ApplicationHelper
def clear_search_params
params.except(:search, :page, :my_required_condition_flag).permit(
:per_page, resource_name => %i[order direction]
)
end
end
end
If you do this, you'll get a new, related error. This time from /app/helpers/administrate/application_helper.rb:48. The method there is called sanitized_order_params, and can be overriden similarly:
module Admin
module ApplicationHelper
# ...
def sanitized_order_params(page, current_field_name)
collection_names = page.item_includes + [current_field_name]
association_params = collection_names.map do |assoc_name|
{ assoc_name => %i[order direction page per_page] }
end
params.permit(:search, :my_required_condition_flag, :id, :page, :per_page, association_params)
end
end
end
And with that, you should be clear of errors.
Admittedly, this is not very nice fix. Ideally Administrate should be providing some better way to override this list of allowed search params. Fancy submitting a PR? ;-)

Failing Rails test based on lack of set session variable - how to set in Rails 5.2?

I have the following very simple test which has always passed.
test "should get index" do
sign_in #user
get companies_url
assert_response :success
end
However, I'm now receiving the following error.
Error:
CompaniesControllerTest#test_should_get_index:
ActionView::Template::Error: No route matches {:action=>"show", :controller=>"companies", :id=>nil}, missing required keys: [:id]
app/views/layouts/application.html.erb:53:in `_app_views_layouts_application_html_erb__2582383126246718868_70101260952060'
test/controllers/companies_controller_test.rb:16:in `block in <class:CompaniesControllerTest>'
What I've changed in my app is that I've built a sidebar (which I load via application.html.erb so it's loaded on all views (new, show, edit), which lets a user switch between various companies they "own" - which changes a session variable which we use to alter the content of the sidebar.
If we dig into the line that seems to be failing app/views/layouts/application.html.erb:53 this is how it looks:
<div class="list-group-item">
<%= link_to 'Company', company_path(session[:current_company]) unless current_user.companies.empty? %>
</div>
If I remove this link_to line then the tests pass.
My guess is that the show view is trying to load, including the sidebar which doesn't have a session[:current_company] set so the view crashes. However, in Rails 5.2 you cannot set/test session variables as far as I understand, so I'm wondering what the best way for me to set the testing up to make this pass? I do set a value for this session within my application controller a user signs in though:
def after_sign_in_path_for(resource_or_scope)
# Set a default current company scope for a use after signing in
session[:current_company] = current_user.companies.first.id unless current_user.companies.empty?
companies_path
end
Perhaps in the link_to from from within the sidebar I could add a default value to make sure we're always sending in a company, regardless of whether it's the session[:current_company] or not?
Altering the line to skip creating the link if there is no session variable present
<%= link_to 'Company', company_path(session[:current_company]) unless current_user.companies.empty? || !session[:current_company] %>
Seemed to do the trick and make all the tests pass. Would love some feedback on whether this is a good solution or not though! :)

Setting up to invoke form helpers from Rails console

I'd like to experiment with using form helpers inside the Rails console, but my simple minded approach of doing extend ActionView::Helpers::FormHelper didn't work. For example, subsequently calling form_for resulted in the error NoMethodError: undefined method 'dom_class' for main:Object. Using include produces the same results.
Is there an "easy" way to enable me to call form helpers from the console? I'd prefer to do this without any dependency on controller or view files, if possible.
Peter, I'm happy to solve your problem.
For simple view helpers, it's very easy
> app.helper.link_to 'root', app.root_path
# <a href = ....> # as expected
However your specific case is not that easy as form_for needs view_context and then a controller instance to work.
# Get a controller instance at first. Must do it.
# Without this step `app.controller` is nil
> app.get app.root_path
# Use an instance variable but not variable!
> #user = User.last
# Get the view
# Note you need to take the trouble to define url manually
# because console can only reach path through app instance
> app.controller.view_context.form_for(#user, url: app.user_path(#user)){|f| f.input :name}
# Job done
You need to use include, not extend:
irb(main):007:0> include ActionView::Helpers::FormHelper
=> Object
irb(main):008:0> form_for
ArgumentError: wrong number of arguments (0 for 1)

Rspec2 + Rails4: Testing for displayed model fields in form partial

Question
I'd like to write tests that check the model fields that are displayed in my "show" and "form" partials. I succeeded for "show", not for "form".
Main constrain: The solution must be able to loop through an Array that contains each names of the model fields.
I believe this case can be interesting for anyone that is trying to shorten his test script files, while having many fields, and having a complete control over what's displayed and what's not, so I'll put some efforts trying to find a solution, with your help if you please :)
Form view
Nothing fancy
= form_for #user do |f|
= f.select :field_1, options_from_collection_for_select ...
= f.text_field :field_2
...
Actual situation
I found an easy way for the "show" partial, here is how my spec file looks like:
def user_fields_in_show_view
[:field_1, :field_2, ..., :field_n]
end
it 'display fields' do
user_fields_in_show_view.each do |field|
User.any_instance.should_receive(field).at_least(1).and_call_original
end
render
end
This works well.
-
But the exact same technique does not work in the "form" partial, using the same code
def user_fields_in_form_view # List of fields need to be different, because of the associations
[:field_1_id, :field_2, ..., :field_n]
end
it 'display fields' do
user_fields_in_form_view.each do |field|
User.any_instance.should_receive(field).at_least(1).and_call_original
end
render
end
It whines like this:
Failure/Error: Unable to find matching line from backtrace
Exactly one instance should have received the following message(s) but didn't: field1_id, field_2, ..., field_n
# Backtrace is long and shows only rspec/mock, rspec/core, rspec/rails/adapters, and spork files
What I tried so far
1- I commented out the stub part of my tests and output rendered to the console, to manually check what's generated by my view, and yes the fields are correctly generated.
2- I replaced User.any_instance by the model I assign to the view, error is slightly different but it still not working
it 'display fields' do
user = create :user
assign :user, user
user_fields_in_form_view.each do |field|
user.should_receive(field).at_least(1).and_call_original
end
render
end
Gives:
Failure/Error: user.should_receive(field).at_least(1).and_call_original
(#<User:0x0000000506e3e8>).field_1_id(any args)
expected: at least 1 time with any arguments
received: 0 times with any arguments
3- I change the code so the it is inside the loop, like this:
user_fields_in_form_view.each do |field|
it 'display fields' do
user = create :user
assign :user, user
user.should_receive(field).at_least(1).and_call_original
render
end
end
Same result as above
And I run out of options. I suspect the internals of FormBuilder to play a bad trick on me but I can't figure it out, I'm not very knowledgeable with those yet. Thanks for reading
I usually try to write unit test as simple as possible. Loops in unit tests don't add much readability and are not very good practice in general. I'd rewrite the test like this:
it 'should display user name and email' do
# note: `build` is used here instead of `create`
assign :user, build(:user, first_name: 'John', last_name: 'Doe', email: 'jd#example.com')
render
rendered.should have_content 'John'
rendered.should have_content 'Doe'
rendered.should have_content 'jd#example.com'
end
Thus, we're not limiting the view in how it should render the first and the last name. For example, if our view uses the following (bad) code in order to render user's full name, then your test will fail, but my test will work just fine, because it tests the behaviour of the view, not its internals:
<%= user.attributes.values_at('first_name', 'middle_name').compact.join(' ') %>
Moreover, multiple assertions in one test is a bad smell too. Going one step further, I'd replace this test with three smaller ones:
it "should display user's first name" do
assign :user, build(:user, first_name: 'John')
render
expect(rendered).to include 'John'
end
it "should display user's last name" do
assign :user, build(:user, last_name: 'Doe')
render
expect(rendered).to include 'Doe'
end
it "should display user's email" do
assign :user, build(:user, email: 'jd#example.com')
render
expect(rendered).to include 'jd#example.com'
end
========
UPD: Let's make it more dynamic in order to avoid tons of repetition. Tis doesn't answers why your spec fails, but hopefully represents working tests:
%i(first_name last_name email).each do |field|
it "should display user's #{field}" do
user = build(:user)
assign :user, user
render
expect(rendered).to include user.public_send(field)
end
end
In order to make these tests more reliable, make sure that user factory doesn't contain repetitive data.
I am not quite sure how you build your form, but if you use the form_for, simple_form_for or formtastic_form_for helpers, actually you are using a different object. You write something like (assume the basic form_for)
= form_for #user do |f|
and all methods are relayed to object f. Now f.object will point to #user, but is it a copy of #user or #user itself, I don't know. So I would expect that User.any_instance should work.
Regardless, when doing a view test, it is not important how the contents of a field are set, it is important that the contents of a field are set correctly. Suppose you build your forms yourself, you switch to another library to build your forms, and all your tests break, because it retrieves the data differently. And that should not matter.
So I am with #DNNX, and in your view tests you should test the content of the rendered HTML and not how the data is retrieved.

RSpec View testing: How to modify params?

I am trying to test my views with RSpec. The particular view that is causing me troubles changes its appearance depending on a url parameter:
link_to "sort>name", model_path(:sort_by => 'name') which results in http://mydomain/model?sort_by=name
My view then uses this parameter like that:
<% if params[:sort_by] == 'name' %>
<div>Sorted by Name</div>
<% end %>
The RSpec looks like this:
it "should tell the user the attribute for sorting order" do
#Problem: assign params[:sort_for] = 'name'
render "/groups/index.html.erb"
response.should have_tag("div", "Sorted by Name")
end
I would like to test my view (without controller) in RSpec but I can't get this parameter into my params variable. I tried assign in all different flavours:
assign[:params] = {:sort_by => 'name'}
assign[:params][:sort_by] = 'name'
...
no success so far. Every idea is appreciated.
If its a controller test then it would be
controller.stub!(:params).and_return {}
If its a helper test then it would be:
helper.stub!(:params).and_return {}
And its a view test it would be:
view.stub!(:params).and_return {}
If you get warning like below.
Deprecation Warnings:
Using `stub` from rspec-mocks' old `:should` syntax without explicitly enabling the syntax is deprecated. Use the new `:expect` syntax or explicitly enable `:should` instead. Called from /home/akbarbin/Documents/Office/projects/portfolio/spec/views/admin/waste_places/new.html.erb_spec.rb:7:in `block (2 levels) in <top (required)>'.
If you need more of the backtrace for any of these deprecations to
identify where to make the necessary changes, you can configure
`config.raise_errors_for_deprecations!`, and it will turn the
deprecation warnings into errors, giving you the full backtrace.
1 deprecation warning total
Finished in 4.86 seconds (files took 4.72 seconds to load)
You can change it into
allow(view).to receive(:params).and_return({sort_by: 'name'})
That's because you shouldn't be using params in your views.
The best way I see it to use an helper.
<div>Sorted by <%= sorted_by %></div>
And in one of your helper files
def sorted_by
params[:sorted_by].capitalize
end
Then you can test your helpers quite easily (because in helpers tests, you can define the params request.
The easy way is to just do this:
helper.params = {:foo => '1', :bar => '2'}
But in general it's better to be more integration-y and not "stub" values when it's feasible. So I prefer to use controller tests with integrate_views. Then you can specify your params to the get, and test that the entire flow works, from sending params to the controller, to having them processed by the controller, and finally to rendering.
I also generally prefer to pull out view logic into helpers, which can be easier to test.
For instance, say I have a helper called selection_list, which returns a Hash whose "selected_preset" key relies on params[:selected_preset], and defaults to 42 if an empty value is specified for the param.
Here's a controller test where we've called integrate_views (you could of course do the same thing with an actual view test, if you're into that).
describe '#show' do
describe 'selected_preset' do
it 'should default to 42 if no value was entered' do
get :show, :params => {:selected_preset => ''}
response.template.selection_list[:selected_preset].should == 42
This integration test will alert me if some part of this functionality breaks. But I also would ideally like to have some unit tests to help me pinpoint that breakage.
I'll start by having the helper use an instance variable instead of directly accessing params. I'll change the above code by adding a single line directly below the get, as follows:
describe '#show' do
describe 'selected_preset' do
it 'should default to 42 if no value was entered' do
get :show, :params => {:selected_preset => ''}
assigns[:selected_preset].should == 42 # check instance variable is set
response.template.selection_list[:selected_preset].should == 42
Now I also can easily perform a helper unit test:
describe MyHelper do
describe '#selection_list' do
it 'should include the selected preset' do
assigns[:selected_preset] = 3
helper.selection_list[:selected_preset].should == 3
Another method of setting view params:
controller.request.path_parameters[:some_param] = 'a value'

Resources