Spec Your Views 12
I meant to work on this post… oh about 7 months ago.
Way back in January (7 months ago), Jamis Buck posted an article titled, Testing your views, which gave a few tips on using Test::Unit to, as the title suggests, test your views.
While, I’m not going to rewrite everything that Jamis wrote, I’d like to show you how to test these views with RSpec. (you might take a moment to quickly read his post…)
In this example, I’m going to show you how we’re able to write specs for the following RHTML, which you’ll notice matches the code that he wrote tests for.
<% if @user.administrator? %>
Hi <%= @user.name %>! You appear to be an administrator.
<%= link_to "Click here", admin_url, :id => "admin_link" %>
to see the admin stuff!
<% end %>
Jamis writes, “The only really significant thing you ought to be testing here is that the admin link only shows up for administrators. “
So, let’s do just that, but with RSpec.
I’m not sure how Jamis is handling his view tests, but we’re going to approach our view specs, much like we approach our controller specs, with the use of mocks and stubs, because we really don’t need to spec any of our models at this level in the application.
Tip: Write specifications for your models… in your model specs not in your controller or view specs.
The first thing that we’re going to do is setup a custom spec helper, because for something like an mocked user, will probably get reused in other areas of the user interface. Spec helpers are essentially modules that you can include in your RSpec descriptions (the block that starts with describe) and reuse.
In this spec helper, I’m going to include two methods, to mock the User model and stub out any of the methods that are necessary for spec’n this view.
module MockUserHelper
def mock_normal_user
user = mock(User)
user.stub!(:administrator?).and_return(false) # <--- NOT an admin
user.stub!(:name).and_return('David Chelimsky')
return user
end
def mock_admin_user
user = mock(User)
user.stub!(:administrator?).and_return(true) # <--- IS an admin
user.stub!(:name).and_return('Aslak Hellesoy')
return user
end
end
In the mock_normal_user method, we’re constructing a mock object and stubbing out the methods that we see are being called in the RHTML code. In mock_admin_user, we’re basically doing the same thing, but just stubbing the administrator? method to return true for this mock user.
By stubbing these methods, we’ll be able to send a non-ActiveRecord object to the view and have it render without knowing the difference. For example, the if @user.administrator? condition will return true or false, depending on how we stubbed it.
For more information on mocks and stubs, read here.
Now that we have our spec helper, let’s go ahead and dive into a few specifications for the view.
describe "index page" do
include MockUserHelper
it "should render an admin link for an admin user" do
assigns[:user] = mock_admin_user
render 'index'
response.should have_tag('a#admin_link')
end
it "should not render an admin link for a normal, non-admin user" do
assigns[:user] = mock_normal_user
render 'index'
response.should_not have_tag('a#admin_link')
end
end
Please note: This code example is only longer than the one shown by Jamis because he didn’t include how he setup all his user sessions/objects. ;-)
When these specs are run, we can see the following results.

Pretty output courtesy of RSpec + TextMate bundle
Great, we’ve been able to write specifications for our Rails views without a lot of pain. Stay tuned for more posts on this topic as I continue writing about how Designers and Developers can work together, in harmony. (see my last post on this topic)
For more information on adopting RSpec, please visit the RSpec project homepage.
Enjoying the content? Be sure to subscribe to my RSS feed.





Good article, I seriously think that view testing is REALLY important and too often overlooked. And if you do true TDD, the view tests should come before your code!
Regarding your great example, I believe we could even check on the link itself by doing something like that:
Nice article.
Havent really done much with regards to view specs, but would love to start a project by writing them first.
ZenTest’s view tests let you do cool stuff like:
assert_links_to admin_url
wish they’d add that to the rspec view tests.
You may also use
user = mock_model(User, :administrator? => true, :name => ‘Aslak Hellesøy’)
Joe, using RSpec custom matchers, it would be quite possible to write something like:
Custom matchers are very easy to write. See the RSpec rdoc for more information.
Nice, clear article. Thanks Robby.
And thanks for spelling my name right. Although I do question the wisdom of making Alsak an admin instead of me.
The only thing I’d add is that BDD is a design process, and “behaviour-driven” means write the examples of behaviour before the code that implements it.
I think this is much easier to grok when dealing with models than it is with views, in large part because we don’t want to specify every character we stick in a view – just the stuff w/ business value – in order to avoid binding the examples too closely to the code.
But difficult to grok or not, an important benefit of BDD gets lost when you’re spec’ing existing code rather than implementing bits of code against bits of specs in a red-green-refactor cycle.
Thanks for the feedback people. Some good ideas on how to extend and improve the examples.
Matt,
Indeed. This is a very important thing to point out. While this is the ideal, it isn’t always what happens. For example, we’ve been contracted on several occasions to integrate testing into existing Rails applications quite often. So, we’ve had to work on a process for diving into an existing application and begin working on specs.
David C,
You’re welcome and I made this decision with extreme caution, but don’t worry… I have big plans for you in future code samples. ;-)
Sorry for being a bit OT, but this quote reminds me of a pet peeve of mine with Rspec and Rcov.
When you run a controller spec with rcov, it’ll show code coverage for any files that were executed, including any models that it uses (assuming I don’t mock them all, which I usually do). If I’m spec’ing my controller, I want to restrict coverage reporting to that controller file only. I don’t care if any libs, helpers or models were used, I want their coverage to be 0% until I spec them explicitly.
I think if there was a way to tell rcov inside a spec which files are to include in the coverage report, and the true coverage was reported, alot of projects would see their coverage drop significantly. Even though it would result in a bit of a drop this would give people a better indication of how well they really are covering their code, rather than assuming because one spec ran some unrelated code that it was being fully tested.
Thanks Robby,
That was really helpful, especially since I was wondering “wtf is the point of view testing. How would I do it in rSpec?”
I’d love some more examples over time. Thanks again.
Double thanks from me.
For some reason, existing ‘blog docs’ on Rspec just are not as clear and well written as yours ;)
Keep it up, you’ve got another subscriber.
sudara
I wonder about using mocks in cases like this.
I tend to use mocks when the original asset itself is difficult to test, but if the ActiveRecord object is just as easy to test as the mock, why not get more bang for your testing buck?
That is, I don’t understand why decoupled test are a positive in and of themselves.
Let me give an example. Let us say I changed the interface on User and on the methods relating to permissions. My User unit test break, and I fix those. But now my views are actually broken, but because the decoupled view tests test against the mock, they still pass: a false positive. And I have no indication from my testing framework that my application is broken.
It almost seems as though mocks violate DRY since they force me to specify the interface to my models twice, both in the model itself and in the mocks: a maintenance burden.
I tend to use fixtures with mocked methods (admin?) for view testing, so that i do not need to mock every field and can be sure my test subjects stay valid