#38 RSpec - Views Part 2

Including A Layout

In
Post #4,
we introduced a #title helper method. To this method, we would pass the title of our post and it would then be displayed in a <h1> as well as in the <title>. How can we test this? One way is to render the application layout in our post index tests. In order to do this, we specify the layout to be rendered in addition to the main view file.

In order to get the tests to pass, we need to write the helper method and modify the application layout (Code not shown yet. See next sections). Those two tests are now reaching outside of the post index specs and testing other things. Depending on your preference this may be what you want.

spec/views/posts/index.html.haml_spec

# index.html.haml_spec
describe "posts/index.html.haml"do
...
context 'when an admin is not signed in'do
...
context "and no posts are present"do
before do
assign :posts, []
render :file => "posts/index.html.haml", :layout => "layouts/application.html.haml"end
it "displays the title as an h1"do
rendered.should have_selector 'h1',
:content => 'Building This Site',
:count => 1end
it "displays the title in <title>"do
rendered.should have_selector 'title',
:content => 'aRailsDemo | Building This Site'end

Message Expectations

Let's take another approach and keep our index specs focused. In this case, we just want to make sure that the #title helper method is being used. For this we set a
message expectation with #should_receive.

We can add various levels of detail with #should_receive. At minimun, we could just state what method should be called. However, we can also state what arguments the method should receive and how many times the method is called. In addition, if the result of that method call is important for the rest of the spec to run, we can state what the output will be.

Since we haven't written #title yet, we need to stub that method out at the start of our tests.

# index.html.haml_spec.rb
describe "posts/index.html.haml"do
...
before do
view.stub(:title)
end
context 'when an admin is not signed in'do
...
context "and no posts are present"do
before do
assign :posts, []
end
it "uses the #title helper method"do# view.should_receive(:title)
view.should_receive(:title).with "Building This Site"# view.should_receive(:title).once.with "Building This Site"# view.should_receive(:title).exactly(1).times.with "Building This Site"# view.should_receive(:title).with("Building This Site").and_return('yee haw')
render
end
___________________________________________________
-# index.html.haml
- title "Building This Site"
-# ...

Helper Methods

We now turn our attention to the #title helper. To test helpers, RSpec provides us with
#helper.
This method "returns an instance of ActionView::Base with the helper being specified mixed in, along with any of the built-in rails helpers." In addition, it includes ApplicationHelper by default. If we have a method in another helper module that we need to access, then we can just include that module in our test file.

Stubbing A Template

Let's look at the application layout now. This layout will render a sidebar partial. This partial doesn't exist yet, so we can use RSpec's #stub_template. This will accept a hash of the partial we're stubbing and the response of the stub.

Mock Models And Forms

Next, we look at forms. We need to provide a model to Rail's form_for helper. The thing to keep in mind here is that mock_model by itself will act as an existing object. So the form that is generated will be an update form. If we want the model to act like a newly created object, we use #as_new_record.

These test, however, are testing some internals of Rails form_for method ('Is form_for rendering the form with the right action?') An alternative test is to just make sure that form_for was called.

Which ever way we choose, we can then test to make sure certain elements are in place. To get around the fact that our mock_model has no attributes, we have to use #as_null_object. Without this, when Rails asks "Hey mock object. What's your :title attribute?", RSpec will say "Hacka please. I ain't expectin' no :title message!" and raise an error. (This isn't a problem if we're using stub_model.)

Working With Simple Form

Since we're using the
simple_form gem
for this site, we'll switch to that now. SimpleForm will look at our existing model to determine what kind of input fields to generate. Therefore we generate our Post and Section models now and switch to using stub_model instead of mock_model in our specs.* We also setup our Post and Section relationships.

(* Things get buggy when using mock_model and stub_model for the same model within or between spec files. This may be limited to just when Autotest and Spork are running. I switched to using only stub_model for all of the post specs.)

An RSpec Helper Module

RSpec allows us to include custom modules so that they are available in all of our tests. So let's create a module that will contain our model stub. This stub is a post that has a single section. It also has some default values which we can change if we need to.

Form Partials

Our post form will render a partial that contains the fields for the sections' fields. The trick here is that we need to pass in a form builder object to this partial. Fortunately, we can call
form_for directly in our tests
to create that builder object. SimpleForm can then use this builder object to create the fields that we are testing.

Conclusion

So are views worth testing especially in isolation from the controllers and models? Point's of view vary widely. In the end, it's just another tool for developing software. From the RSpec book: "The only way to really get a feel for the benefits of them [view tests] is to learn to write them well. And only once you really understand how they fit in the flow are you going to be able to make well-grounded decisions about if and when to use them." To each, his or her own.

(Addition view and helper tests for this post can be found at our
Github repo.)