How to validate locals of render template in rspec - ruby-on-rails

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.

Related

rspec 'allow' stub isn't setting variables

I have a controller function:
def update
#simulation = Simulation.find(params[:id])
#simulation.next
puts "--"
puts #simulation.dirty?
puts #simulation.save
if (#simulation.save && #simulation.dirty?)
render :partial => 'show', :object => #simulation
end
end
And an rspec test:
it "should render a partial when the record is dirty" do
allow(#simulation).to receive('dirty?') { true }
put :update, :id => #simulation.id, :format => 'js'
expect(response).to render_template( :partial => 'show' )
end
The test is failing to render the view because the if check isn't passing because it won't return true for #simulation#dirty? even though the function is stubbed. I can see this because of the puts in the controller. Any ideas why it's not working?
The instance variable #simulation you are stubbing does not belong to the controller instance, but to the rspec test case class instance. Try #simulation.dirty? within it block of rspec, after the allow method call. I guess it returns true. The #simulation in the controller is not stubbed, though. They are two different objects.
If you want to stub #simulation in the controller's update method, you should stub all the instance of the Simulation class. Try using allow_any_instance_of instead of allow(#simulation).
allow_any_instance_of(Simulation).to receive(:dirty?).and_return(true)
https://github.com/rspec/rspec-mocks#settings-mocks-or-stubs-on-any-instance-of-a-class

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

Rails 4 - rendering JSON from a view

I'm having the worst time rendering a .json.erb file from my controller while being able to test it with RSpec. I have api_docs/index.json.erb and the following controller:
class ApiDocsController < ApplicationController
respond_to :json
def index
render file: 'api_docs/index.json.erb', content_type: 'application/json'
end
end
The explicit render file line seems unnecessary, but if I don't do that or render template: 'api_docs/index.json.erb', then I get an error about "Missing template api_docs/index". Likewise if I do have to pass the file name, it sucks even more that I have to give the exact directory--Rails should know that my ApiDocsController templates live in the api_docs directory.
If I have render file or render template, then I can visit the page and get the JSON contents of my index.json.erb file, as expected. However, this RSpec test fails:
let(:get_index) { ->{ get :index } }
...
describe 'JSON response' do
subject {
get_index.call
JSON.parse(response.body)
}
it 'includes the API version' do
subject['apiVersion'].should_not be_nil
end
end
It fails on the JSON.parse(response.body) line and if I raise response.body, it's an empty string. If I do render json: {'apiVersion' => '1.0'}.to_json in the controller, then the test passes just fine.
So, how can I always render the JSON template when I go to /api_docs (without having to put .json at the end of the URL), and in a way that works both in the browser and in my RSpec test? And can I render the template without having to have some long render call in which I pass the full path of the view?
Actually since you're already using respond_to :json in your controller you can use just a render method to choose your template and, as you probably know, if the template have the same name of the controller method you should be able to suppress the whole render method.
If you just remove the render line, what's the result?
Part of my solution was based on this answer to another question: adding defaults: {format: :json} to my routes file lets me go to /api_docs and see the JSON when the action is just def index ; end with no render. The RSpec test still fails though. The full line from my routes file: resources :api_docs, only: [:index], defaults: {format: :json}.
Thanks to this guy with the same problem and his gist, I added render_views to my describe block and got my test to pass:
describe ApiDocsController do
render_views
...
let(:get_index) { ->{ get :index } }
describe 'JSON response' do
subject {
get_index.call
JSON.parse(response.body)
}
it 'includes the API version' do
subject['apiVersion'].should_not be_nil
end
end

RSpec controller testing: missing template on create

I have an interesting situation. I am testing the following simple create action:
# will only be accessed via Ajax
def create
click = Click.new(params[:click])
click.save # don't really care whether its success or failure
end
Then I have the following very simple controller spec:
require 'spec_helper'
describe ClicksController, "creating a click" do
it "should create a click for event" do
xhr :post, :create, :click => {:event_id => 1}
# more test to come...
end
end
Seems trivial, yet I get the following:
Missing template clicks/create
Any tips would be appreciated.
Add to the controller action:
render :nothing => true
This one will automatically create the appropriate server's respone. More here
You will get this error if your controller renders only JSON or XML, yet you don't specify a format in the spec; your request then defaults to unsupported HTML. In that case, simply specify the supported format when you invoke the controller method from your spec. For example, change this:
post :create, registration: #user_hash
to this:
post :create, registration: #user_hash, format: :json
If you do not render anything in a controller action, rails will attempt to default to rendering a template (in this case clicks/create). I'd suggest rendering back at least a success message like so:
render :json => {:success => true}
Building on megas's answer, if you're looking to test a controller action that's only accessed via a UJS link and only has a .js.erb template, I'd put this in the controller to avoid breaking your UJS functionality:
respond_to do |f|
f.html { render nothing: true } # prevents rendering a nonexistent template file
f.js # still renders the JavaScript template
end
This will enable you to call the controller action by simply calling ActionController::TestCase::Behavior's get/post/put/delete methods instead of needing to call xhr, because it will successfully call the method, render nothing, and continue on, while leaving your UJS behavior intact.

Understanding Rails 3's respond_with

Utilizing ActionController's new respond_with method...how does it determine what to render when action (save) is successful and when it's not?
I ask because I'm trying to get a scaffold generated spec (included below) to pass, if only so that I can understand it. The app is working fine but, oddly, it appears to be rendering /carriers (at least that's what the browser's URL says) when a validation fails. Yet, the spec is expecting "new" (and so am I, for that matter) but instead is receiving <"">. If I change the spec to expect "" it still fails.
When it renders /carriers that page shows the error_messages next to the fields that failed validation as one would expect.
Can anyone familiar with respond_with see what's happening here?
#carrier.rb
validates :name, :presence => true
#carriers_controller.rb
class CarriersController < ApplicationController
respond_to :html, :json
...
def new
respond_with(#carrier = Carrier.new)
end
def create
#carrier = Carrier.new(params[:carrier])
flash[:success] = 'Carrier was successfully created.' if #carrier.save
respond_with(#carrier)
end
Spec that's failing:
#carriers_controller_spec.rb
require 'spec_helper'
describe CarriersController do
def mock_carrier(stubs={})
(#mock_carrier ||= mock_model(Carrier).as_null_object).tap do |carrier|
carrier.stub(stubs) unless stubs.empty?
end
end
describe "POST create" do
describe "with invalid params" do
it "re-renders the 'new' template" do
Carrier.stub(:new) { mock_carrier(:save => false) }
post :create, :carrier => {}
response.should render_template("new")
end
end
end
end
with this error:
1) CarriersController POST create with invalid params re-renders the 'new' template
Failure/Error: response.should render_template("new")
expecting <"new"> but rendering with <"">.
Expected block to return true value.
# (eval):2:in `assert_block'
# ./spec/controllers/carriers_controller_spec.rb:81:in `block (4 levels) in <top (required)>'
tl:dr
Add an error hash to the mock:
Carrier.stub(:new) { mock_carrier(:save => false,
:errors => { :anything => "any value (even nil)" })}
This will trigger the desired behavior in respond_with.
What is going on here
Add this after the post :create
response.code.should == "200"
It fails with expected: "200", got: "302". So it is redirecting instead of rendering the new template when it shouldn't. Where is it going? Give it a path we know will fail:
response.should redirect_to("/")
Now it fails with Expected response to be a redirect to <http://test.host/> but was a redirect to <http://test.host/carriers/1001>
The spec is supposed to pass by rendering the new template, which is the normal course of events after the save on the mock Carrier object returns false. Instead respond_with ends up redirecting to show_carrier_path. Which is just plain wrong. But why?
After some digging in the source code, it seems that the controller tries to render 'carriers/create'. There is no such template, so an exception is raised. The rescue block determines the request is a POST and there is nothing in the error hash, upon which the controller redirects to the default resource, which is the mock Carrier.
That is puzzling, since the controller should not assume there is a valid model instance. This is a create after all. At this point I can only surmise that the test environment is somehow taking shortcuts.
So the workaround is to provide a fake error hash. Normally something would be in the hash after save fails, so that kinda makes sense.

Resources