Rails relies on a standard project structure and a strong set of conventions to keep things neat and tidy - models separated from controllers, configuration in another folder structured just so. The exception that proves the rule in this case, though, would have to be Rails view helpers. Helpers tend to be a dumping ground for all the random bits of view logic, formatting, and utility code that accumulate in every web application, and if you’re not disciplined (and most of us aren’t),
app/helpers can degenerate into a jungle quickly.
But helpers fill a necessary role in our applications by removing presentation logic from our templates and moving it to methods which makes it easier to test. So even though they’re going to be hard to organize almost by definition, we can still be disciplined about testing them. In other words: your view helpers might still look like spaghetti, but at least we can make sure it’s well-tested spaghetti.
The Only Two Things You Need to Know About Helpers
Faced with a collection of arbitrary functions, many developers skip testing helpers altogether. A lot of my older applications have zero tests for helpers, and it was mostly because the benefits just didn’t seem to justify the effort required. That changed the day I came to two important realizations. First, understand that helpers are just Ruby mixins. From a testing perspective, there’s nothing special or unusual about them, and that means that we can apply the same tools and techniques that we already use to test other kind of mixins.
Second, since they’re just modules mixed into the view, there are only two types of helpers we need to consider: methods that depend on the thing they’re mixed into, and methods that don’t. The rest of this post will explain how to know which type of method you’re dealing with and how to write a test for it once you do.
Testing Standalone Helpers
A standalone helper for our purposes is essentially a pure function - a method whose return value is based solely on the inputs given to and which produces no other side effects. Common examples of these include methods used to format numbers, dates and text stored in a model for display or those that wrap other Rails view helpers to add more specific functionality - usually anywhere from 50-90% of helpers. The block below shows just a sample of the types of methods I mean:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
While the Rails view mixes in all your helper modules with every request, helper tests based on ActionView::TestCase only include the helper module currently under test. The result, though, is that methods from the helper are available as part of the test, so we can call them directly as you see in the test below.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
Testing methods like this is simple - given known inputs, make assertions about the outputs. Another common Rails pattern is a helper method that takes a block parameter. Methods like these can be used to generate markup that will be wrap code defined in the template. One example might be a helper that takes a block and prepares a form tag specifically tailored to a given model class and interface type.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
When testing a method like this, it’s possible to make assertions about the parameters passed to the block argument in addition to the return value as you see in the example test.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
Testing Helpers That Depend on the View
In most cases, I find it cleaner to write helper methods as pure functions by passing in all the state needed to evaluate it. But in certain cases, it’s better to let helpers be helpers by not being ignorant about their relationship to the view. Case in point: a friend of mine shared an example with me a few weeks ago where he was working on building up a string of DOM classes in the view. He’d written a method like the one below to generate the class for the link to the current URL:
1 2 3 4 5
This could have been written by passing in the Boolean result of
current_page?(path), but the route he chose was definitely cleaner and more Rails-like. The problem that he was having, though, was: how best to test this?
My advice to him was first to understand what you don’t need to test. Specifically, you don’t have to care how
current_page? arrives at a result, you only care about the result itself and what your code does with it. Once you realize that, then all you need is to stub the possible values of that method and make assertions about what your code returns.
Here again, the helper module and all the Rails standard view helpers are mixed directly into the test case, so the test behaves just like a Rails view. We can stub
current_page? directly in the test like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
Is Testing Helpers Worth Your Time?
I don’t test every single helper that I write. There’s just very little return on the time you spend writing a test for a function that formats the current date or wraps a call to
link_to. Through experience though, I’ve become more aware when I find myself writing a helper that probably needs to be tested. Any methods with the following characteristics are usually good candidates:
- Contains conditionals or
- Long methods (> 10 LOC including any executed private methods)
- Uses String composition to generate complex markup
- Takes more than one parameter