how to test partial with instance variables - ruby-on-rails

so i need to test a partial. the partial is rendered by specific action, its something like messages box in facebook. my test looks like this:
describe 'partials/_partial.js.erb' do
it 'displays stuff' do
render
end
end
i run it, and i know it does what i want because i immediately get
Failure/Error: if #items.count > 0
ActionView::Template::Error:
undefined method `count' for nil:NilClass
i do not need to hear that it is a bad practice to use instance vars in a partial, it is already there and i need to work with it. so how do i set #items here...?
UPDATE:
controller action looks like this:
def controller_method
#items = items_method
render_version(:partial => "partials/_partial.js.erb")
end

It looks like you're using rspec. In that case, any instance variables you define during your test will be available for your views (regardless if it's a partial or not).
describe "users/_messages" do
before(:each) do
# This is available in your view.
#items = []
end
it "renders without error when #items is empty" do
# Will pass, #items is available
expect { render }.to_not raise_error
end
it "shows a count of how many messages there are" do
# You can modify it before rendering
#items << Message.new
render
expect(rendered).to have_content "You have 1 message"
end
end

Related

Rspec Test controller render partial with locals

I have controller action like
def get_status
status_name = current_user.status
status_updated_time = current_user.updated_at
render :partial => 'show_status', :locals => {status_name: status_name, status_updated_time: status_updated_time}
end
here I am planning to test local variable values which are passing via render partial. i.e
status_name, status_updated_time.
Could you please let me know how to write rspecs for render partial with locals in controller.
I would move variable logic into a separate method:
def get_status
render partial: 'show_status', locals: get_status_from(current_user)
end
protected
def get_status_from(user)
{ status_name: user.status, status_updated_time: user.updated_at }
end
and test that method instead.
I would say that to test the controller, what you're after is a basic feature/integration spec wherein you can simply look for the content held by your partial.
feature 'SomeController' do
background do
# setup data
# and anything else you need for authentication, etc. as your site dictates
end
scenario 'viewing the get status page' do
visit some_controller_get_status_path
expect(page).to have_text('WHATEVER IS IN THE LOCAL VAR')
end
end
I prefer to use feature specs over controller specs as I seek (but often fail!) to keep my controllers so simple that there is not really much to test in them. With feature specs, I feel like I'm getting more from the test in terms of how my app works, etc.
EDIT: sorry ... hit enter too early :).
For a controller, you could directly test the var value along the lines of:
describe "Your Controller", :type => :controller do
describe "GET get_stuff" do
it "assigns a value to status_name" do
get :get_status
expect(assigns(:status_name)).to eq(['VALUE'])
end
end
end
That may not be 100% spot-on for a controller spec (again, I don't use them a lot) but I think it should get you on your way should you go controller spec over feature/integration spec.
you could do something like
it "should render correct partial for get_status" do
controller.should_receive(:render).with({
:partial => '_show_status', #here you will have to give the full path like <controller_name>/_show_status
:locals => {status_name: <name>, status_update_time: <time>}
})
get 'get_status'
end

view.stub in rails partial rspec gives 'undefined method view_context'

using Rails 3.2.11
I have a couple of view rspec tests where I need to stub the 'current_user' call.
I've used this successfully in a regular view test like so:
require 'spec_helper'
describe "projects/_my_project.html.erb" do
before(:each) do
#client = FactoryGirl.create(:user)
view.stub(:current_user) { #client }
end
describe "new proposals notice" do
it "does not display new_propsals if in state posted and clicked after last submitted" do
#my_project = FactoryGirl.build(:project, status: "posted", last_proposals_click: "2012-02-02 14:01:00", last_proposal_submitted: "2012-02-02 14:00:00")
render :partial => "/projects/my_project", :locals => { :my_project => #my_project }
rendered.should_not have_content "You received new proposals"
end
end
end
Current_user is defined by Devise in controllers/helpers.rb (in the gem). I use it all over the place as current_user (as a method, not instance) in the view or controller.
The problem seems to be around view in view.stub being nil here, is there another object that is used in case of partials?? I simply don't understand why this works perfect in a regular view and not in a partial.
I get:
Failure/Error: view.stub(:current_user) { #client }
NoMethodError:
undefined method `view_context' for nil:NilClass
Here is the line from the view where current_user is used for completeness:
<% if my_project.user_id == current_user.id %>
Does anyone know how I can get it to stub current_user successfully, I'm at a loss here...
thanks
Turns out that moving view.stub(:current_user) { #client } into each 'it' block solved the problem; it does not seem to work if it is in the 'before:each/all' block.

How to validate locals of render template in rspec

I wonder how to validate the locals passed to render template in controller
Controller:
def lelf_panel
# ...
if some_condition
locals_hash = some_very_long_hash_A
else
locals_hash = some_very_long_hash_B
end
render :partial => "left_panel", :layout => false, :locals => locals_hash
end
Current Spec:
it 'should render correct template for lelf_panel' do
# ...
get 'left_panel'
response.should render_template('system/_left_panel')
end
Now I need to finish Rcov for this controller so I need to add/modify spec to cover both 'some_condition' results. and I want to validate 'lelf_panel' locals passed to render, as if I only validate the render_template, partial page rendered for both result are the same.
I check the 'render_template' in rspec docs in
http://rubydoc.info/gems/rspec-rails/2.8.1/RSpec/Rails/Matchers/RenderTemplate:render_template
it only provide and 2nd params for message, so how can I test the locals passed to render?
Instead of using the render_template matcher, you can use an expectation on the controller object.
it 'should render correct template for lefl_panel' do
# ...
allow(controller).to receive(:render).with no_args
expect(controller).to receive(:render).with({
:partial => 'system/_left_panel',
:layout => false,
:locals => some_very_long_hash_A
})
get 'left_panel'
end
Same as #ryan-ahearn 's answer with suggestions from #user2490003 's comment - but all put into something more flexible and for RSpec 3.
# Safe to set globally, since actions can either render or redirect once or fail anyway
before do
allow(controller).to receive(:render).and_call_original
end
describe "get left panel" do
before do
# other setup
get 'left_panel'
end
it 'should render correct template for lelf_panel' do
# Sadly, render_template is primitive (no hash_including, no block with args, etc.)
expect(subject).to render_template('system/_left_panel')
end
it 'should render with correct local value' do
expect(controller).to have_received(:render) do |options|
expect(options[:locals][:key_from_very_long_hash]).to eq('value_of_key_from_very_long_hash')
end
end
end
as far as I know, there is no way to directly examine the locals for a template in the way you're describing.
You could change locals_hash to #locals_hash and then examine the results through assigns( :locals_hash).
Or, you could use selectors on the resulting HTML and check that some indicative content is there -- for instance, if locals_hash affects the title of the page, check that the resulting HTML page title is what you expect.

Testing instance variables in controller with RSpec

Given a controller like this where it creates several instance variables for use by the view, would you generally test that each of those get set properly? It seems like you would want to, but it also seems a little it could be a bit tricky. What's the right approach?
class StaffsController < ApplicationController
def index
set_index_vars
#all_staff = Staff.find_staff_for_business_all_inclusive(current_business_id)
respond_to do |format|
format.html { render :action => "index", :locals => { :all_staff => #all_staff, :all_services => #all_services, :new_vacation => #new_vacation } }
end
end
def set_index_vars
#days_of_week = days_of_week
#first_day_of_week = DefaultsConfig.first_day_of_week
#all_services = Service.services_for_business(current_business_id)
#new_vacation = StaffVacation.new
#has_hit_staff_limit = current_user_plan.has_hit_staff_limit?
end
end
The code is also posted at https://gist.github.com/1018190
If you're going to write a controller spec, then yes, by all means test that the instance variables are assigned. Much of the 'trickiness' can come from dependencies on other models/libraries, so stub out those method calls:
# air code
Staff.stub(:find_staff_for_business_all_inclusive) {array_of_staff}
controller.stub(:days_of_week) {['Monday','Tuesday',....etc...]}
DefaultsConfig.stub(:first_day_of_week) {"Monday"}
Service.stub(:services_for_business).with(some_value_for_the_current_business_id).\
and_return(some_relevant_value)
StaffVacation.stub(:new) {something_meaningful}
controller.stub_chain(:current_user_plan,:has_hit_staff_limit?) {false}
get :index
assigns(:days_of_week).should == ['Monday','Tuesday',....etc...]
# ...etc...
I would split it up as follows: test that the index calls the correct method. Then test whether the method works.
So something like
describe StaffsController do
describe "GET #index" do
it "calls set_index_vars" do
controller.should_receive(:set_index_vars)
get :index
end
# and your usual tests ...
end
describe "#set_index_vars" do
before(:each) do
# stub out the code not from this controller
controller.stub_chain(:current_user_plan, :has_hit_staff_limit?).and_return(false)
.. etc ..
controller.set_index_vars
end
it { assigns(:days_of_week).should == controller.days_of_week }
it { assigns(:has_hit_staff_limit).should be_false
# etc ..
end
end
Hope this helps.
So long as you have good coverage around your method, you can test that your method is being called at the right times, with the right values etc. Something like:
describe StaffsController do
describe "GET #index" do
it "should call set_index_vars" do
controller.should_receive(:set_index_vars)
get :index
end
end
describe "#set_index_vars" do
it "should assign instance variables with correct values" do
# or wtv this is supposed to do
get :index
assigns(:days_of_week).should == controller.days_of_week
# etc ..
end
end
end

Despite a controller error my tests still pass, while visiting the edit url correctly shows an error. Why is this so?

I have a Rails controller where I accidentally defined the 'edit' method inside the 'create' method.
My Controller with the error:
class UsersController < ApplicationController
...
def create
#user = User.new(params[:user])
...
def edit
#user = User.find(params[:id])
#title = "Edit user"
#check = "BORK" # something I added for testing the rendered output
end
end
end
An example test;
it "should have the right title" do
get :edit, :id => #user
response.should have_selector('title', :content => 'Edit user')
end
So when I run the tests (I use rspec) and output the response.body, the User edit.html.erb template is rendered correctly; all the instance variables are visible. So all the tests pass.
Visiting the 'edit' URL correctly shows an error; the template uses #user instance variable, and it's not set correctly. Of course correcting the controller fixes the error.
I don't understand why the tests pass at all and why, in the test, all the instance variable values are visible?
My instinct suggests this is a scope problem? Something about #user being an instance variable, and that in the tests it's set within the scope of the test, but in my controller it's within the scope of the inner 'edit' method? But how does the test even find the 'edit' method? In what scope does that inner 'edit' method exist?
You should realise that the def construct is as much executable code as an if statement. It's not invalid to put it inside another method, but it won't be run until the outer method is called:
>> class Foo
>> def foo
>> def bar
>> end
>> end
>> end
=> nil
>> Foo.instance_methods(false)
=> ["foo"]
>> Foo.new.foo
=> nil
>> Foo.instance_methods(false)
=> ["foo", "bar"]
The reason this was erroring in your browser was because Rails reloads all (most) of your classes each request. So, even if you had visited the create action - which would cause the edit method to be defined - the following request would have unloaded it again.
However in the test environment, if an earlier test had called the create action then that would have defined the edit action for future tests. You would see a different result if your tests were run in a different order (which in itself makes it a bad idea to rely on this).
Generally of course this isn't what you want at all, so just clear it up and move along :)

Resources