Pivotal Labs

Testing Views by Not Testing Views: Or, The Presenter Pattern

edit Posted by Nick Kallen on Friday October 19, 2007 at 06:40AM

Err The Blog asks: "What's the best way to test views?"

I think the best way to test views is not to test views. Extract all logic from the view into a model or presenter where it can be unit tested. Your views are then mostly declarative and there's minimal need to test them.

Here's an example of the "presenter pattern".

def create_or_destroy_friendship_link(friend)
  if current_user.friends_with?(friend)
    destroy_friendship_link(friend)
  else
    create_friendship_link(friend)
  end
end

You don't need a special class to do a Presenter; a good old-fashioned layer of abstraction will do. The basic idea is to write all conditional+iterative view logic in such a way as to never call a Rails helper directly, or generate any HTML directly, or generate any strings directly. The logic merely delegates to other methods closer to the metal.

Tests then become fairly simple. Write tests of the higher-level conditional/iterative logic in terms of the lower-level methods:

describe FriendshipsHelper, '#create_or_destroy...' do
  it "renders create link when two users are not friends" do
    log_in(users(:bob))
    bob.should_not be_friends_with(users(:amy))
    create_or_destroy_friendship_link.should == create_friendship_link
  end
end

This minimizes the need for view specs. I find in practice that a high percentage of view tests slow development down"”they're implemented not to aid development (since you typically debug views in-browser), but to prevent regression (i.e., they minimize the likelihood of introducing defects later). But since views are one of the most variable parts of a web application, regression tests are of the least value.

As a side note, I love integrate_views -- not because I like to make assertions about the view in my controller tests, but because I hate mocks! I want a controller test to fail if I have a syntax error in my view!

Comments

  1. Adam Keys Adam Keys on October 23, 2007 at 03:25PM

    I've thought of doing something like that, but with helpers instead of pushing the logic into a model (which sounds heretical to me) or a presenter (not so bad). So my two questions:

    • Why not helpers?
    • How do you integrate designers into the fold? Do your presenters then render partials that they can edit?

    Also, I'd be interested to see your approach all the way down, i.e. what does create_friendship_link look like?

  2. Alex C Alex C on October 23, 2007 at 07:09PM

    Why not helpers?

    In Nick's code example, they are helpers. Notice the reference to "current_user" -- that's a clear sign that it's in view-land, not model-land.

    How do you integrate designers into the fold? Do your presenters then render partials that they can edit?

    Ah yes, this old bugbear rears its head again. In my experience, UI designers come in two breeds:

    1. Those who stop at mockups, either PSDs or HTML+CSS, and hand those off to the coders, who do the work of translating into dynamically-rendered HTML
    2. Those who are savvy enough to edit code, which means RHTML or Markaby or Ruby or Java, and who aren't afraid to run a test suite or check in to subversion

    I have heard many legends of a mythical beast, elusive as Sasquatch: the Idiot Savant Designer. He is smart enough to learn HTML, JavaScript, DHTML, CSS, and knows how to lay out his pages in reasonably-chunked reusable components. Yet once having learned these high-level concepts, he is reduced to blubbering infancy at the prospect of coding directly in Ruby or Java -- even if that code consists only of functions and print statements. Despite copious references and allusions to this creature in whitepapers and blog posts throughout history, I have never personally made a sighting, nor spoken to anyone who has. As far as I can tell, the ISD is an urban legend, or maybe purposeful disinformation sowed by the promoters of webapp frameworks (in league with the angle bracket industry).

    In short, designers are in the fold in exactly the same way as if we were embedding logic inside template files, only they are writing better code.

  3. nick nick on October 23, 2007 at 09:31PM

    I am a heretic, and I believe that putting "view logic" in the model is perfectly OK. Nevertheless, as Alex points out, the above example does none of that. To totally change the topic, Here is my anti-MVC rant:

    MVC is a sacred cow, but we rarely ask "why?" let alone "should?". Firstly, Rails MVC is a bit of a joke; their are no objects in the view layer, the Controller layer is only vaguely analogous to Controllers in the GUI world, etc.

    Secondly, MVC is not necessarily the best fit for a web app. If you buy into the REST religion, you might think there are only two layers of abstraction--resources and representations.

    Thirdly, many of the most interesting GUI frameworks are not MVC. cf., Morphic which was invented with Self and is now used in Squeak.

    Fourthly, MVC has serious downsides with are rarely acknowledged, namely that there's a) a parallel class hierarchy (often you simply have one view mapping to one model mapping to one controller), and b) if you want to (e.g.) render a heterogeneous list of model objects you need a factory to instantiate appropriate views; that factory needs to break polymorphism and manually dispatch on type.

    In short, MVC is stupid. Heresy, yes.

  4. Adam Keys Adam Keys on October 23, 2007 at 10:05PM

    @Alex OK, I thought they were methods on the Presenters. Cool.

    As to the mythical ISD, I agree that finding someone who can go from IA and visual design all the way down to templates is rare, but not unpossible. I know one fellow who can do just that, and even into the depths of Rails controllers and models.

    On the other hand, front-end developers who are saavy with JS, HTML and CSS are often competent enough to deal with Rails views. Plus, they are far more numerous. Unless we make things hard for them by outputing HTML from helpers and whatnot, they can do everything they're used to doing with a minimum of fuss. But if your helpers rely on partials, then everyone can have their cake, it seems to me.

  5. Riki Riki on October 24, 2007 at 12:22PM

    Man you don't even know how long I've waited for this since disabling my own Movable Type widget (that doesn't work since Haloscan bypasses that code).

  6. Frederic Torres Frederic Torres on November 09, 2007 at 01:05PM

    Generally speaking how do you test a javascript code that run in a view let's say triggered by a click on a button.

    Frederic Torres
    www.InCisif.net
    Web Testing with C# or VB.NET

  7. Chad Woolley Chad Woolley on November 09, 2007 at 05:45PM

    @Frederic:

    There are a couple of approaches. First, you can test the javascript in isolation using jsunit. To test the complete page integration, you can use a tool like Selenium. See Pivotal's seleniumrc-fu project for an integrated package that makes it easy to use these tools ( http://rubyforge.org/projects/pivotalrb/ )

    -- Chad

Add a Comment (MarkDown available)