Read my latest article: Was away on vacation (posted Sun, 11 May 2008 22:33:00 GMT)

Spec Your Views 12

Posted by Robby Russell Thu, 02 Aug 2007 07:00:00 GMT

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.

Subscribe to my RSS feed Enjoying the content? Be sure to subscribe to my RSS feed.
Comments

Leave a response

  1. Avatar
    Matt Aimonetti Thu, 02 Aug 2007 08:03:52 GMT

    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:

    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[href=?]', "#{admin_url}")
    end
  2. Avatar
    Dylan Egan Thu, 02 Aug 2007 09:12:44 GMT

    Nice article.

    Havent really done much with regards to view specs, but would love to start a project by writing them first.

  3. Avatar
    Joe Van Dyk Thu, 02 Aug 2007 09:20:22 GMT

    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.

  4. Avatar
    Yurii Rashkovskii Thu, 02 Aug 2007 09:26:27 GMT

    You may also use

    user = mock_model(User, :administrator? => true, :name => ‘Aslak Hellesøy’)

  5. Avatar
    Luke Redpath Thu, 02 Aug 2007 10:43:43 GMT

    Joe, using RSpec custom matchers, it would be quite possible to write something like:

    
    response.should have_link_to(admin_url)
    

    Custom matchers are very easy to write. See the RSpec rdoc for more information.

  6. Avatar
    David Chelimsky Thu, 02 Aug 2007 11:31:39 GMT

    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.

  7. Avatar
    Robby Russell Thu, 02 Aug 2007 12:57:58 GMT

    Thanks for the feedback people. Some good ideas on how to extend and improve the examples.

    Matt,

    ”...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!”

    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,

    ”...thanks for spelling my name right. Although I do question the wisdom of making Alsak an admin instead of me.”

    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. ;-)

  8. Avatar
    Dan Kubb Thu, 02 Aug 2007 17:00:21 GMT

    Tip: Write specifications for your models… in your model specs not in your controller or view specs.

    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.

  9. Avatar
    Daniel Fischer Mon, 13 Aug 2007 21:57:28 GMT

    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.

  10. Avatar
    Sudara Tue, 04 Sep 2007 13:54:47 GMT

    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

  11. Avatar
    Ara Vartanian Thu, 06 Dec 2007 21:25:40 GMT

    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.

  12. Avatar
    grosser Sun, 30 Mar 2008 10:34:34 GMT

    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

Share your thoughts... (really...I want to hear them)

Comments