Spec Your Views
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.
BDD is still kinky
I still believe that Behavior-Driven Development is kinkier than Test-Driven Development...
Is BDD kinkier than TDD?
If you’re in need of a compelling reason to switch from Test-Driven Development to Behavior-Driven Development... consider this.
“BDD sounds kinkier.” -Michael K. Loukides, Book Editor for O’Reilly
...on that bombshell... I am going to suggest that this be used as the official BDD logo.
There you have it. Deep down… the biggest reason that I switched from Test::Unit to RSpec... was the sex appeal. ;-)
What was your reason?