Like a lot of Rails developers, I rarely write view specs. In almost all the projects I’ve seen and worked with, there have been hardly any view specs that were actively maintained.
There are a few reasons for this: they can be brittle, add little perceived value, or are subsumed by a combination of feature and controller tests. Not least, they can encourage adding business logic to views, which no one does, right?
Last week, however, I found myself wishing for one. I was adding logic to a Rails partial to show different content based on the state of the underlying model.
To complicate matters:
- In this case, the underlying model could be in 3 different states.
- The partial would show as a modal when the user clicked a button nested inside a tab1.
As an example, pretend you’re on a library site2 and want to check out a particular book. The book could be available (at this location or a different one), overdue and/or on hold. It could also be an audiobook, ebook, or a physical copy. You locate the book entry in a list, click the tab for the content type you want, and click “Reserve”. This modal pops up:
The content behind the modal looks something like this:
Yes, I understand this could be factored differently. For instance, the parent view could render the modal differently based on the state, using some form of view model or decorator which encapsulated the correct partial and content, and could be unit-tested independently. However, this code predated my time on the project, and had no test coverage. The approach outlined below would still provide a solid starting point from where to perform the refactor.
Given this situation, a view spec is appealing for lots of reasons:
- It is essentially a unit test. All I need to test is that the view shows the right thing given the appropriate data setup.
- I don’t need to test the entire flow to get to the page, including the brittle interaction involving clicking tabs and launching modals.
- I don’t need to test whether Bootstrap is doing its job.
- I don’t need the content rendered in PhantomJS and verified with Selenium. I’m ultimately comparing text.
As it turns out,
rspec-rails has excellent support for writing view specs. In my case, I could use the
render helper to generate the html for only the partial I’m interested in, not even the entire view that renders it:
In the above sample, the
assign method allows setting up the instance variable that the view will use. In this case, the
@book in the rendered view will be the
available_other_location_book factory object. If the partial used
locals, those could be passed into the
render method just like you would in a Rails view.
rendered variable contains the output of the render, much like
response in a controller test. We can make simple assertions against it by matching on text. In cases where we need to verify more than text, we can parse the
rendered with Nokogiri, and match the output using its powerful helpers like
For example, when the book is available at a different location, I could check for the location selector to be present with the right options:
As a practice, views should be as devoid of business logic as possible, but there are cases where some complexity is unavoidable. View specs provide a faster, simpler alternative to writing feature specs to cover those cases.
1 This was an interaction I had no control over. ↩
2 If this example seems contrived, that’s because it is. The original code is proprietary, so I’ve come up with an analogy, which like all analogies, falls a little short. Analogies, after all, are like ladders.↩