I'm running Rails 3.1.1, RSpec 2.7.0 and HAML 3.1.3.
Say I have the following view files:
app/views/layouts/application.html.haml
!!!
%html
%head
%title Test
= stylesheet_link_tag "application"
= javascript_include_tag "application"
= csrf_meta_tags
%body
= content_for?(:content) ? yield(:content) : yield
app/views/layouts/companies.html.haml
- content_for :content do
#main
= yield :main
#sidebar
= yield :sidebar
= render :template => 'layouts/application'
app/views/companies/index.html.haml
- content_for :main do
%h1 MainHeader
- content_for :sidebar do
%h1 SidebarHeader
And the following spec file:
spec/views/companies/index_spec.rb
require 'spec_helper'
describe 'companies/index.html.haml' do
it 'should show the headers' do
render
rendered.should contain('MainHeader')
rendered.should contain('SidebarHeader')
end
end
When I run RSpec, I get the following error:
1) companies/index.html.haml should show the headers
Failure/Error: rendered.should contain('MainHeader')
expected the following element's content to include "MainHeader":
# ./spec/views/companies/index_spec.rb:7:in `block (2 levels) in <top (required)>'
At first, I thought RSpec was somehow missing the content_for blocks when rendering the view files. However, I was not able to find any issue related to it on RSpec's github repository, so I'm not sure who's to blame here.
One (recent) solution I found is at http://www.dixis.com/?p=571. However, when I try the suggested code
view.instance_variable_get(:#_content_for)
it returns nil.
Is there a way to test content_for in view specs?
Is there a better way to structure my layout files, such that I'm actually able to test them and still achieve the same end result?
Using Rspec 2 with Rails 3, in order to write view specs for usage of content_for, do this:
view.content_for(:main).should contain('MainHeader')
# instead of contain() I'd recommend using have_tag (webrat)
# or have_selector (capybara)
p.s. the value of a content_for(...) block by default is an empty string, so if you want to
write specs showing cases in which content_for(:main) does not get called, use:
view.content_for(:main).should be_blank
Your spec could be written as:
it "should show the headers" do
render
view.content_for(:main).should contain('MainHeader')
view.content_for(:side_header).should contain('SidebarHeader')
end
This way your spec shows exactly what your view does, independent of any layout. For a view spec, I think it's appropriate to test it in isolation. Is it always useful to write view specs? That's an open question.
Instead if you want to write specs showing what the markup served to the user looks like, then you'll want either a request spec or a cucumber feature. A third option would be a controller spec that includes views.
p.s. if you needed to spec a view that outputs some markup directly and delegates other markup to content_for(), you could do that this way:
it "should output 'foo' directly, not as a content_for(:other) block" do
render
rendered.should contain('foo')
view.content_for(:other).should_not contain('foo')
end
it "should pass 'bar' to content_for(:other), and not output 'bar' directly" do
render
rendered.should_not contain('bar')
view.content_for(:other).should contain('bar')
end
That would probably be redundant, but I just wanted to show how render() populates rendered and view.content_for. "rendered" contains whatever output the view produces directly. "view.content_for()" looks up whatever content the view delegated via content_for().
From the RSpec docs:
To provide a layout for the render, you'll need to specify both the template and the layout explicitly.
I updated the spec and it passed:
require 'spec_helper'
describe 'companies/index.html.haml' do
it 'should show the headers' do
render :template => 'companies/index', :layout => 'layouts/companies'
rendered.should contain('MainHeader')
rendered.should contain('SidebarHeader')
end
end
Do not bother with view specs. They're hard to write well, and they don't test enough of the stack to be worth using (especially in view of the difficulty writing). Instead, use Cucumber, and test your views in the course of that.
You generally don't want to test content_for specifically either: that's implementation, and you should instead be testing behavior. So just write your Cucumber stories so they test for the desired content.
If for some odd reason you do need to test content_for, RSpec has a syntax that's something like body[:content_name] or body.capture :content_name depending on the version (or something like that; haven't used it in a while). But consider carefully whether there's a better way to test what you actually want to test.
Related
Im trying to set up a (basic) test for a new feature I am going to implement. I have a job controller and instead of default showing all jobs I like to hide all the ones which is archived. I tried different ways but it seems like i am missing a piece or two of this puzzle. First i tried with calling 'visit' but get the message it does not exist. Second approach is using 'render' but that also ends up in a error saying render does not exists. (can i even use these methods in a controller spec?)
Is it wrong to put this in a controller test?
2 last test are causing errors
require "rails_helper"
require "spec_helper"
describe JobsController, :type => :controller do
context 'GET index' do
it "should be successful" do
get :index
expect(response).to be_success
end
it "renders the index template" do
get :index
expect(response).to render_template("index")
end
it "should not show any archived jobs as default" do
visit jobs_path
page.should have_no_content("Archived")
end
it 'should show the headers' do
render :template => 'job/index', :layout => 'layouts/job'
rendered.should_not contain('Archived')
end
end
end
Capybara is used for feature specs, and its matchers can be used in view specs.
Controller specs, by default, don't actually render the view because they're the wrong place to be checking for page content - https://www.relishapp.com/rspec/rspec-rails/docs/controller-specs
You should probably move some of your tests to feature tests/view tests
I want to store pages in the database that are dynamically added/edited/etc. Along with the page title and content I also supply which layout to use and a controller name and view name in order to support the instantiation of models which the view (e.g. text field in the pages table) will use.
I'm having trouble rendering the text from the database and having the layout still be used.
class AboutController < BaseController
def index
#model_data = ...
render_dynamic_page("about", "index")
end
end
class BaseController < ApplicationController
layout "public"
def render_dynamic_page(controller_name, action_name)
page = Page.where("`controller_name` = :controller_name AND `action_name` = :action_name", { :controller_name => controller_name, :action_name => action_name }).first
render :layout => page.layout_name, :text => page.content
end
end
I'm using :text here and I've also tried :inline but both seem to not render the content within the layout.
How can I accomplish this?
Normally if you want to allow users to edit the html pages in a rails application, and store the pages in a DB, the standard way is to use the template language called liquid.
It allows you to define dynamic content, ideal for CMS systems in rails, I think its better you have a look first.
following are some important links
liquid home page
rails cast about liquid
git hub page
and there are lots of resources in SO itself. :)
I was so close - I just had to switch the order of the parameters to the render method:
render :inline => page.content, :layout => (page.layout_name || "public")
I also added a default layout to use public in case a page.layout_name was not specified in the database. The other thing that's worth mentioning is the difference between :text and :inline - only :inline seems to actually "process" the content as if it were an ERB whereas the :text option just literally outputs the raw text.
Note : I ended up creating an ERB for each page using ERB.new(page.content), caching the list of dynamic ERBs and then pulling the pre-rendered ERB from the cache and displaying it using:
render :inline => the_cached_erb.result(binding), :layout => (page.layout_name || "public")
There is still some weird behavior going on but I think it will work in the end.
Just FYI and for greater tools variety you may want to check out Mercury editor:
http://jejacks0n.github.io/mercury/
https://github.com/jejacks0n/mercury
http://railscasts.com/episodes/296-mercury-editor
I have this problem related to testing model errors with Mocha:
This is my controller (Api / Artist controller):
class Api::ArtistsController < ApplicationController
respond_to :json
def create
artist = Artist.new(params[:artist])
if artist.save <-- This is where the test fails
render :json=>artist
else
respond_with artist
end
end
end
This is my model (Artist Model):
class Artist < ActiveRecord::Base
include Core::BaseModel
attr_accessible :name
has_many :albums
validates :name, :presence=>true, :uniqueness=>{:case_sensitive=> false}
default_scope where :deleted=>false
end
This is the test where it fails, about Artist controller:
it "should not save a duplicated artist" do
Artist.any_instance.stubs(:is_valid?).returns(false)
Artist.any_instance.stubs(:errors).returns({:name=>[I18n.t('activerecord.errors.messages.taken')]})
post :create, :format => :json
expect(response).not_to be_success
expect(response.code).to eq("422")
results = JSON.parse(response.body)
expect(results).to include({
"errors"=>{
"name"=>[I18n.t('activerecord.errors.messages.taken')]
}
})
end
When I run the tests, this is the error I get on the above test:
Failure/Error: post :create, :format => :json
NoMethodError:
undefined method `add_on_blank' for {}:Hash
# ./app/controllers/api/artists_controller.rb:17:in `create'
# ./spec/controllers/api/artists_controller_spec.rb:56:in `block (3 levels) in <top (required)>'
I'm starting to use Mocha, so I don't know if there's a way to test the json result for the specific case when I want to test the validation for the duplicated name.
ActiveRecord::Base#errors (i.e. Artist#errors) isn't a simple hash. It's supposed to be an instance of ActiveModel::Errors. You're stubbing it with a hash, and ActiveRecord is trying to call add_on_blank on it, which is failing.
I don't think save invokes is_valid? at all, and I suspect it's running the validations and then trying to call add_on_blank to append an error, but since you've stubbed out errors, that's failing.
This isn't really a good way to test the controller. It's making too many assumptions about the internals of Artist. You're also testing things that aren't part of the controller at all; errors isn't referenced anywhere in the action. The only behavior worth testing in the controller is whether or not it creates an Artist; if that Artist fails to save, that it renders JSON with it; and if the save succeeds, that it redirects. That's all of the controller's responsibility.
If you want to test that errors are rendered a certain way, you should write a separate view spec. If you want to test that missing fields generate errors, you should write a model spec. If you don't want to write a view spec, it's still sufficient to rely on the model to populate errors (tested in a model spec), and in your controller, just test that render is called with json set to the Artist instance.
Generally speaking it's best to avoid stubbing as much as possible, but in this case, the only things I'd consider stubbing are Artist.new to return a mock, and save on that mock to return false. Then I'd check to make sure it rendered with the mock.
The easier option is to just create an actual Artist record, then call post with duplicate params to trigger a validation failure. The downside is that you hit the database, and avoiding that in a controller spec is laudable, but generally more convenient. You could instead do that in a Capybara feature spec if you want to avoid DB hits in your controller specs.
If you want to try testing the way you are, you can manually create an instance of ActiveModel::Errors and populate that, or stub methods on it, and stub out Artist.any_instance.stubs(:errors) to return your mock with ActiveModel::Errors-compatible behavior, but that's a lot of mocking.
One final tip: don't use post :create, :format => :json. Use xhr :post, :create to generate a real Ajax request rather than relying on a format param. It's a more robust test of your routing and response code.
I want to test that my edit recipe page renders using rspec, though it doesn’t route to
recipes/edit
it routes to recipes/id/edit (id being replaced with a number)
my current test looks like this
describe "Show Edit Recipe Page" do
it "should display edit recipe page" do
get :edit
response.should be_success
response.should render_template(:edit)
end
end
how can i test this page correctly, at the moment my tests are failing
Problem
Your example doesn't include the code needed to actually test a controller object. RecipeController is not defined in your spec.
Solution
Make sure your controller specs live under spec/controllers or have an explicit type: :controller set. Then, actually describe a controller, either using the implicit subject or by setting up a controller instance in a before or test block. As the most basic example:
describe RecipeController do
# test something using the implied RecipeController.new
end
More Reading
RSpec Controller Specs
The get needs the id of the recipe passed in the params hash:
let(:recipe) { Factory.create(:recipe) }
it "should display edit recipe page" do
get :edit, :id => recipe.id
response.should be_success
response.should render_template(:edit)
end
I try to assign to result of rendering an action to an element on page. That should be an easy task but the following statement:
$(".box:first").after("<%= escape_javascript(render :action => "new" ) %>");
will result in the following error message:
ActionView::Template::Error (undefined method `formats' for nil:NilClass):
1: $(".box:first").after("<%= escape_javascript(render(:action => "new") ) %>");
The same code works if i try to render a partial using :partial => "new" (given that the partial with the given name exists)
The error message will be the same if I change ':action' f.e. to ':foobar'.
Any ideas what i am doing wrong or is this simple a bug in rails? (3.0.7)
It is a bug in your application. Actually it is a bug in your new action in controller
Result of rendering of an action? That's probably not the very best idea. As you said, the code works when you use partial, and that's what you should use.
This is from official Rails guides:
Using render with :action is a
frequent source of confusion for Rails
newcomers. The specified action is
used to determine which view to
render, but Rails does not run any of
the code for that action in the
controller. Any instance variables
that you require in the view must be
set up in the current action before
calling render.
http://guides.rubyonrails.org/layouts_and_rendering.html#using-render