Rails testing miniseries part 6: View testing

View testing allows you to test out complicated or critical view code, but has probably the lowest payoff in terms of problems prevented per developer minute spec.

For Rails view testing is done either in with the Test::Unit controller “functional tests”, or by RSpec “view specs” (and “helper specs” – I think of helpers as being a part of the views because their sole purpose in life is to extract relatively complex code out of views, though as a general rule we prefer to test separate classes separately).

What view tests can cover

  • Using the right data and associations from models
  • HTML tags/structure as expected
  • Scripting as expected

So, about those as expecteds. Thing is, while you can test that your views spit out the “correct” stuff – the correct tags, the correct RJS code – all you're doing there is verifying that it puts out what you wanted the code to put out.

And that's not the same thing as verifying that it puts out what will actually work the way you want in the browser.

Testing RJS, in particular, is usually only useful for checking that your view is applying the appropriate one of several different cases, if your code has more than one; to actually check that it works, you need to use an in-browser acceptance testing tool (covered later below).

Similarly, testing that the right tags and attributes go out doesn't mean it's laid out and rendered the way you want, and we need to not overdo it – see below.

Points to remember

  • Concentrate on likely bug hotspots
  • Often just special case & regression tests (for sites with fairly straightforward UIs)

For many applications the payoff for testing your views is relatively poor, so we need to talk about what's really worth doing.

Don't overdo it

As for HTML tags and the rest… Let me emphasise that you have to keep view testing strictly minimal. It's easiest to understand why by imagining a totally excessive view testing scenario.

So take the extreme case where you could check that every tag and every attribute is as you expect. Think about what that test code would look like and what information it would contain, and you realise that all you'd be doing is rewriting the actual view code out in a different syntax/language with all of the conditionals and loops inlined.

That'd be a complete waste of time, so the moral is simply: test only what needs to be tested.

If you're worried that your paginator stuff is going to mess up, go ahead and check the count the number of rows output. If you're thinking that a helper or model attribute might get misused and put the wrong CSS classes on some critical elements, go ahead and check it got them the right way around.

But don't waste your time testing every view and every partial and every tag, concentrate on code that you think is likely to produce bugs – that's what we're here for.

Aside from the low productivity, testing things that don't really need to be tested would make your tests very fragile – basically any change to the view would mean changes to the test too, and if you do that, you're going to find people write the same misunderstandings and misthinkings into the test code as the view itself.

Recommended coverage

So definitely, not every view needs view tests – and in fact, for most sites, very few views really need view specs. And if you're short on development time, in most apps view testing is usually by far the best place to skimp, especially as browser-based testing is far more effective.

My personal experience is that view testing is works out to be worthwhile for annoying regressions (especially repeat offenses – common if you have separate UI and backend people) plus expected trouble spots (complex helpers, most often) – and not a lot else. Focus strictly on likely problem areas.

I recommend that instead of spending time on view tests, you instead work on integration tests or even better, full-stack integration tests, which I'll cover later in the series.

Because I don't generally have separate view specs, I generally break the strict idea of testing every component separately and turn on view rendering in my controller tests, so that I still have a relatively cheap and quick way of catching most of the seriously broken view code. This is the default for Test::Unit and is activated in RSpec by calling integrate_views in the test context.