Rspec - Test that rails view renders a specific partial - ruby-on-rails

I have a rails 3.2.13 app running rspec-rails 2.14.0 and am trying to confirm that a view renders a particular partial in my test. It actually does work, but I need to add this test. Here's what I have so far:
require 'spec_helper'
describe 'users/items/index.html.haml' do
let(:current_user) { mock_model(User) }
context 'when there are no items for this user' do
items = nil
it 'should render empty inventory partial' do
response.should render_template(:partial => "_empty_inventory")
end
end
end
This runs without error, but does not pass. The failure is:
Failure/Error: response.should render_template(:partial => "_empty_inventory")
expecting partial <"_empty_inventory"> but action rendered <[]>
Thanks for any ideas.
EDIT
This works for me, but Peter's solution is better...
context 'when there are no items for this user' do
before do
view.stub(current_user: current_user)
items = nil
render
end
it 'should render empty inventory partial' do
view.should render_template(:partial => "_empty_inventory")
end
end
For some reason it was counter-intuitive to me to have to call render on a view, but there you go...

So the way one usually tests whether a particular partial is rendered in a view spec is by testing the actual content of the partial. For example, assume that your _empty_inventory parial has the message 'There is no inventory'. Then you might have a spec like:
it "displays the empty inventory message" do
render
rendered.should_not have_content('There is no inventory')
end
Alternately, you could use a controller spec, in which case you need to call the 'render_views' method when setting up the spec. Then you can do something similar to
it 'should render empty inventory partial' do
get :index, :user_id => user.id
response.should render_template(:partial => "_empty_inventory")
end
Assuming you've set up the state for the contoller spec.

Related

Rspec controller test: undefined method 'orders_path'

Writing some controller tests, using render_views to check some partial rendering....
describe PromoCodeController do
render_views
describe "GET 'show" do
... a bunch of tests
it "renders 'used' partial when promo code has already been used" do
#promo_code = create(:promo_code)
#user.stub(:promo_used?).and_return(true)
get 'show', :slug => #promo_code.slug
expect(response).to render_template(:partial => 'promo_code/_used')
end
which loads in the _used partial
<article>
<p><%= #promo.description.html_safe %></p>
<p>Sorry, it appears this promo code has already been used. Please try again or contact us directly.</p>
<%= link_to "View Order", orders_path(#order), class: "box-button-black", data: { bypass: true } %>
</article>
but breaks with:
undefined method `orders_path' for #<#<Class:0x007fd4069d06e8>:0x007fd401e3e518>
Any ideas on how to either
(a) ignore the Rails link, it's irrelevant to the test
(b) include something in the test to recognize that link
(c) stub it (last resort i think)
Everything I've tried so far doesn't get past the error.
EDIT:
orders_path was wrong, it should be order_path. After changing that I get:
ActionView::Template::Error:
No route matches {:controller=>"order", :action=>"show", :id=>nil}
So the partial is looking for #order. I tried setting it with controller.instance_variable_set(:#order, create(:order)), but in the partial it comes back as nil.
A quick test by adding<% #order = Order.last %> in the view partial passes green. How to pass the var #order into the _used partial is now the question.
Instead of manually setting the spec type you can set it based off file location
# spec_helper.rb
RSpec.configure do |config|
config.infer_spec_type_from_file_location!
end
describe 'GET SHOW' do
run this in a before block
before do
controller.instance_variable_set(:#order, create(:order))
end
it "renders 'used' partial when promo code has already been used" do
promo_code = create(:promo_code)
#user.stub(:promo_used?).and_return(true)
# check if #order variable is assigned in the controller
expect(assigns(:order).to eq order
get 'show', slug: promo_code.slug
expect(response).to render_template(:partial => 'promo_code/_used')
end
end
First off, I needed to change it to order_path, orders_path was wrong. Doh.
Than I needed to stub some methods to get around the error
ActionView::Template::Error:
No route matches {:controller=>"order", :action=>"show", :id=>nil}
Ultimately, stubbing the method assign_promo_to_users_order which assigns a complete order to the current_user did the trick:
it "renders 'used' partial when promo code has already been used" do
#promo_code = create(:promo_code)
#user.stub(:promo_used?).and_return(true)
User.any_instance.stub(:assign_promo_to_users_order).and_return(create(:order, :complete))
get 'show', :slug => #promo_code.slug
expect(response).to render_template(:partial => 'promo_code/_used')
end
Try adding the spec's type.
I believe that the action controller URL helpers are included into the spec type.
Try:
describe SomeController, type: :controller do

RSpec matcher for request rendering text

Is it possible to examine if get request rendered text?
I know there are hacks like response.body == 'any string' but it does not interest me. I'm just wondering if there is "RSpecâ„¢" way to do it.
Having this rspec:
RSpec.describe MyController, type: :controller do
controller do
def index
render text: 'Hellow'
end
end
describe 'rendering' do
subject { get :index }
it { is_expected.to render_template(text: 'Hellow')}
end
end
I would love to be able to call it { is_expected.to render_template(text: 'Hellow')}. It raises:
Failure/Error: it { is_expected.to render_template(text: 'Hellow') }
ArgumentError:
Unknown key: :text. Valid keys are: :layout, :partial, :locals, :count, :file
or maybe it { is_expected.to render_template('Hellow')}
Failure/Error: it { is_expected.to render_template('Hellow') }
expecting <"Hellow"> but rendering with <[]>
Is there any RSpecâ„¢ way to accomplish it?
Testing expect(response.body).to eq('Hellow') is totally appropriate.
The reason is_expected.to render_template isn't working is you aren't rendering a template. If your controller omitted an explicit render call, Rails would render the index template for you, and you could test render_template(:index). You could also render template: :foo and then test render_template(:foo) if you wanted to render a nonstandard template. But when you render text: 'Hellow', you aren't using templates; you're explicitly setting the response body to the text you specify.
If you do render a template, and you want to test the content rendered by that template, that's when render_views comes into play, as gotva mentioned. Even then, you'd be checking for content in response.body, as you can see in RSpec's own examples. As your templates get complicated, the controller specs aren't the appropriate place for this and you should start writing view specs using assert_select or something similar.

Optimize Rails RSpec Tests

I'm working on a test for my Rails 4 app and I'm pretty new to using RSpec. I have a controller named AppsController which has the standard index, new, show, create... methods and they all work the way Rails suggest Etc. "new" creates a new instance of the object and create actually saves it, show, shows it and index shows all of the object. Here are my current tests can anyone see any potential problems or things that i could improve?
FactoryGirl.define do
factory :developer do
email 'example#me.com'
password 'new_york'
password_confirmation 'new_york'
tos '1'
end
factory :app do
name 'New App'
tos '1'
end
factory :invalid_app, parent: :app do
name 'nil'
tos '0'
end
end
require 'spec_helper'
def create_valid!
post :create, app: app_attributes
end
def create_invalid!
post :create, app: app_invalid_attributes
end
def show!
get :show, id: app
end
def update_valid!
put :update, id: app, app: app_attributes
end
def update_invalid!
put :update, id: app, app: app_invalid_attributes
end
def delete!
delete :destroy, id: app
end
def http_success
expect(response).to be_success
end
def expect_template(view)
expect(response).to render_template(view)
end
describe AppsController do
render_views
before(:each) do
#developer = FactoryGirl.create(:developer)
#developer.confirm!
sign_in #developer
end
let(:app) { FactoryGirl.create(:app, developer: #developer) }
let(:app_attributes) { FactoryGirl.attributes_for(:app) }
let(:app_invalid_attributes) { FactoryGirl.attributes_for(:invalid_app) }
describe 'GET #index' do
it 'responds with an HTTP 200 status' do
get :index
http_success
end
it 'renders the :index view' do
get :index
expect_template(:index)
end
it 'populates #apps with the current_developers apps' do
app = FactoryGirl.create(:app, :developer => #developer)
get :index
expect(assigns(:app)).to eq([app])
end
end
describe 'POST #create' do
context 'with valid parameters' do
it 'creates a new app' do
expect { create_valid!
}.to change(App, :count).by(1)
end
it 'redirects to the new app keys' do
create_valid!
expect(response).to redirect_to keys_app_path(App.last)
end
end
context 'with invalid parameters' do
it 'does not create the new app' do
expect { create_invalid!
}.to_not change(App, :count)
end
it 'renders the :new view' do
create_invalid!
expect_template(:new)
end
end
end
describe 'GET #show' do
it 'responds with an HTTP 200 status' do
show!
http_success
end
it 'renders the :show view' do
show!
expect_template(:show)
end
it 'populates #app with the requested app' do
show!
expect(assigns(:app)).to eq(app)
end
end
describe 'PUT #update' do
context 'with valid parameters' do
it 'locates the requested app' do
update_valid!
expect(assigns(:app)).to eq(app)
end
it 'changes app attributes' do
update_valid!
expect(app.name).to eq('Updated App')
end
it 'redirects to the updated app' do
update_valid!
expect(response).to redirect_to app
end
end
context 'with invalid parameters' do
it 'locates the requested app' do
update_invalid!
expect(assigns(:app)).to eq(app)
end
it 'does not change app attributes' do
update_invalid!
expect(app.name).to_not eq('Updated App')
end
it 'renders the :edit view' do
update_invalid!
expect_template(:edit)
end
end
end
describe 'DELETE #destroy' do
it 'deletes the app' do
expect { delete!
}.to change(App, :count).by(-1)
end
it 'redirects to apps#index' do
delete!
expect(response).to redirect_to apps_url
end
end
end
count should have been changed by -1, but was changed by 0 - on DELETE #destroy
expecting <"new"> but rendering with <[]> - on POST #create
expected: "Updated App"
got: "New App" - on PUT #update
expecting <"edit"> but rendering with <[]> - on PUT #update
expected: [#<App id: nil, unique_id: "rOIc5p", developer_id: 18, name: "New App">]
got: nil - on GET #index
Small thing - your #http_success method is testing the exact same thing twice.
You could also factor out the references to app by putting a #let statement right after your #before block:
let(:app) { FactoryGirl.create(:app, developer: #developer) }
then in your specs, just
it 'renders the :show view' do
get :show, id: app
expect_template(:show)
end
Edit:
Then the order of operations will be 1) the #developer is created in the #before block, 2) the spec is entered, 3) at the first reference to app in the spec, the #let block will create an instance of an app.
That means you can't factor out the app creation in the #index spec, because in that case the spec will call the action before it creates the app.
A few things I thought of, reading your code:
You don't need to include parens on method calls taking no arguments. Just http_success will work.
You should get try to use the modern RSpec expectation syntax consistently. Instead of assigns(:app).should eq(app), use expect(assigns(:app)).to eq(app). (There's one exception to this, which is expectations on mocks (ie. should_receive(:message)), which will only take on the modern expect-to syntax as of RSpec 3.
For controller specs, I like to create little methods for each action that actually invokes the action. You'll notice you call get :show, id: app several times in the GET #show specs. To DRY up your specs a little more, you could instead write the following method within the describe block:
def show!
get :show, id: app
end
Try to use one Hash syntax consistently. What's more, Rails 4 can't be run with Ruby 1.8, so there's (nearly) no reason to use the hash-rocket Hash syntax.
If I'm getting really, really picky, I usually consider instance variables in a spec to be a smell. In almost all cases, instance variables should be refactored to a memoized let/given blocks.
If I'm getting really, really, really picky, I prefer to think of controller specs such as yours as strictly a unit test of the controller, not an integration test (that's what Capybara is for), and as such you shouldn't be exercising your model layer at all. You should only be testing that your controller is sending the right messages to the model layer. In other words, all the model layer stuff should be stubbed out. For example:
describe 'GET #show' do
let(:app) { stub(:app) }
before do
App.stub(:find).and_return(app)
end
it 'populates #app' do
get :show, id: app
assigns(:app).should eq(app)
end
end
I know this last is a personal preference, not a metaphysical truth or even necessarily a wide-spread standard convention, so you may choose to take it or leave it. I prefer it, because it keeps my specs very speedy, and gives me a very clear heuristic for when my controller actions are doing too much, and I might need to consider refactoring. It could be a good habit to get into.
First, I'm not certain but I suspect your invalid app factory may be wrong. Did you mean
factory :invalid_app, parent: :app do
name nil
tos '0'
end
nil as a ruby NilClass not "nil" as a string?
As for other comments about cleanup and stuff, here are a few of my thoughts.
You can avoid the need for some of your helper methods and duplication by using before blocks for each describe. Taking just your index tests you could have something more like
describe 'GET #index' do
before do
get :index
end
it 'responds with an HTTP 200 status' do
http_success
end
it 'renders the :index view' do
expect_template(:index)
end
it 'populates #apps with the current_developers apps' do
expect(assigns(:app)).to eq([app])
end
end
Notice also that you don't need to recreate app because your let is doing it as necessary.
On the failures, I suspect the delete count change could be failing because inside the expectation, the test framework is creating a new app (from the let) and then deleting it leading to a count change of 0. For that test, you need to make sure you're app is created outside of your expectation. Because you are using let, you could do that like this:
describe 'DELETE #destroy' do
it 'deletes the app' do
# ensure that app is already created
app
expect {
delete!
}.to change(App, :count).by(-1)
end
end
alternatively, change the let to a let! which will force the creation before the specs actually run.
As for other failures, thought #DanielWright suggested the helper methods, I find those complicate the debug. I can't see where you set the app name to "Updated App", for example. Perhaps a clearer test (for that particular one) would not use the helper methods but could be more explicit. Something like
describe 'PUT #update' do
let(:app_attributes) { FactoryGirl.attributes_for(:app, name: 'The New App Name') }
before do
put :update, id: app, app: app_attributes
end
context 'with valid parameters' do
it 'locates the requested app' do
expect(assigns(:app)).to eq(app)
end
it 'changes app attributes' do
# notice the reload which will make sure you refetch this from the db
expect(app.reload.name).to eq('The New App Name')
end
it 'redirects to the updated app' do
expect(response).to redirect_to app
end
end
end
For the other errors, you might want to start debugging your code. Are you certain it should work? Have you looked at output logs? Maybe the tests are doing there job and finding errors in your controller code. Have you done any step-through debugging?

rspec testing ajax response (should render a partial)

I want to test that my controller action is rendering a partial.
I've poked around and I can't seem to find anything that works.
create action:
def create
#project = Project.new...
respond_to do |format|
if #project.save
format.js { render :partial => "projects/form" }
end
end
end
spec:
it "should save and render partial" do
....
#I expected/hoped this would work
response.should render_partial("projects/form")
#or even hopefully
response.should render_template("projects/form")
#no dice
end
If you're looking for a REAL answer... (i.e. entirely in RSpec and not using Capybara), the RSpec documentation says that render_template is a wrapper on assert_template. assert_template (according to the docs) also indicates that you can check that a partial was rendered by including the :partial key.
Give this a go...
it { should render_template(:partial => '_partialname') }
Update see bluefish's answer below, it seems to be the correct answer
Would you consider using Capybara for your integration testing? I found ajax difficult to test with rspec alone. In your case I'm not even sure you are getting a response back yet. In capybara it waits for the ajax call to finish and you can call the page.has_xxxx to see if it was updated. Here is an example:
it "should flash a successful message" do
visit edit_gallery_path(#gallery)
fill_in "gallery_name", :with => "testvalue"
click_button("Update")
page.has_selector?("div#flash", :text => "Gallery updated.")
page.has_content?("Gallery updated")
click_link "Sign out"
end
another great way to test your ajax controller method is to check the assignments which are later used to render the result. Here is a little example:
Controller
def do_something
#awesome_result = Awesomeness.generete(params)
end
JBuilder
json.(#awesome_result, :foo, :bar)
Rspec Controller Test
describe :do_something do
before do
#valid_params{"foo" => "bar"}
end
it "should assign awesome result" do
xhr :post, :do_something, #valid_params
assigns['awesome_result'].should_not be_nil
end
end

RSpec controller test fails - how to load mock data?

I have 2 simple RSpec tests i've written for a small rails app i've done to learn rails. I originally had a mock setup for my Link class but was getting the same issue.
This is my test code:
require 'spec_helper'
describe LinksController do
render_views
before(:each) do
link = Link.new
link.stub!(:title).and_return("Reddit")
link.stub!(:url).and_return("http://www.reddit.com")
link.stub!(:created_at).and_return(Time.now)
link.stub!(:updated_at).and_return(Time.now)
link.stub!(:user_id).and_return("1")
link.stub!(:id).and_return("1")
link.save
user = User.new
user.save
end
it "renders the index view" do
get :index
response.should render_template('links/index')
response.should render_template('shared/_nav')
response.should render_template('layouts/application')
end
it "renders the show view" do
get :show, :id => 1
response.should render_template('links/show')
response.should render_template('shared/_nav')
response.should render_template('layouts/application')
end
end
I'm new to both rails and RSpec, not sure what I should be doing to get this to work. What is the best way to test this show method from my LinksController when you need data? I tried mock_model too but maybe I was using it wrong.
You can see all the app code on Github
The problem is that you are stubbing a model, so it's not stored in the database. That means that when you call get :show, :id => 1 the query to the database returns nothing and your tests fail. Stubbing is great when you want to fake a response or an object without using the database, but if you are depending on actual Rails code that uses the database you can't use this method because nothing exists in the database. To fix this I would drop the stubbed models entirely and actually create them.
require 'spec_helper'
describe LinksController do
render_views
before(:each) do
user = User.create
#link = Link.create :title => "Reddit",
:url => "http://www.reddit.com",
:user => user
end
it "renders the index view" do
get :index
response.should render_template('links/index')
response.should render_template('shared/_nav')
response.should render_template('layouts/application')
end
it "renders the show view" do
get :show, :id => #link.id
response.should render_template('links/show')
response.should render_template('shared/_nav')
response.should render_template('layouts/application')
end
end
You should also eventually look into Factory gems like Sham and Factory Girl to simplify the creation of test data.

Resources