Rails prides itself on sane defaults, but also provides hooks for customizing the framework by providing Ruby blocks in your configuration files. Most of this code begins and ends its life simply and innocuously. Sometimes, however, it grows. Maybe it’s only 3 or 4 lines, but chances are they define important behavior.
Pretty soon, you’re going to want some tests. But while testing models and controllers is a well-established practice, how do you test code that’s tucked away in an initializer? Is there such thing as an initializer test?
No, not really. But that’s ok. Configuration or DSL-style code can trick us into forgetting that we have the full arsenal of Ruby and OO practices at our disposal. Let’s take a look at a common idiom found in initialization code and how we might write a test for it.
Configuring Asset Hosts
Asset host configuration often start as a simple String:
Eventually, as the security and performance needs of your site change, it may grow to:
1 2 3 4 5 6 7 8 9
Rails accepts an asset host
Proc which takes two arguments – the path to the source file and, when available, the request object – and returns a computed asset host. What we really want to test here is not the assignment of our
Proc to a variable, but the logic inside the
Proc. If we isolate it, it’s going to make our lives a bit easier.
Since Rails seems to want a
Proc for the asset host, we can provide one. Instead of embedding it in an environment file, we can return one from a method inside an object:
1 2 3 4 5 6 7 8 9 10 11 12 13
It’s the exact same code inside the
#configuration method, but now we have an object we can test and refactor. To use it, simply assign it to the
asset_host config variable as before:
At this point you may see an opportunity to leverage Ruby’s duck typing, and eliminate the explicit
Proc entirely, instead providing an
AssetHosts#call method directly. Let’s see how that would work:
1 2 3 4 5 6 7 8 9
Since Rails just expects that the object you provide for the
asset_hosts variable respond to the
#call interface (like
Proc itself does), you can simplify the configuration:
Now lets wrap some tests around
AssetHosts. Here’s a first cut:
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 26 27 28 29 30 31
It’s not magic, but the beauty of first-class objects is they have room to breathe and help present refactorings. In this case, you can apply the Composed Method pattern to
Guided by tests, you might end up with an object that looks like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
Since the external behavior of
AssetHosts hasn’t changed, no changes to the tests are required.
By making a small leap – isolating configuration code into an object – we now have logic that is easier to test, read, and change. If you find yourself stuck in a similar situation, with important logic stuck in a place that resists testing, see where a similar leap can lead you.