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!
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:
Also, I’d be interested to see your approach all the way down, i.e. what does
create_friendship_linklook like?December 12, 2007 at 11:50 pm
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.
Ah yes, this old bugbear rears its head again. In my experience, UI designers come in two breeds:
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.
December 12, 2007 at 11:50 pm
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.
December 12, 2007 at 11:50 pm
@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.
December 12, 2007 at 11:50 pm
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).
December 12, 2007 at 11:50 pm
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
http://www.InCisif.net
Web Testing with C# or VB.NET
December 12, 2007 at 11:50 pm
@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
December 12, 2007 at 11:50 pm