TDD Action Caching in Rails 3
March 28, 2012
[caching
]
[rails
]
[ruby
]
[test-driven-design
]
On my current project, we needed to prove that an action cache was working as expected. Alas, the blogosphere had either out-of-date or unhelpful information. So, after many experiments, we came up with an RSpec test that does what we want. It seems ugly to me, and I hope there's a better way. The names have been changed to protect the guilty. Any resemblances to actual classes and methods are purely coincidental.
We needed to confirm that a certain action was cached. This action is preview
in the brands controller. Using the usual Rails url helpers, we construct some fixture data.
Then we wrote our first test:
This won't work at all, however; because, in the test environment, caching is turned off.
So, we need an around
block to temporarily turn caching on:
That's great, but the default cache store is the :null
store, which, as its name implies, does nothing.
Better. But our tests still won't run because while ActionController uses the cache_store
, Observers
and Sweepers
use Rails.cache
and that is only updated at boot time.
Did I mention that Rails.cache is an accessor for the global, constant, RAILS_CACHE
. Ugh.
So, now, we can implement our method
But that is still not enough. caches_action
has an interesting performance enhancement; it doesn't actually set up the action caching unless caching is enabled at class load time. Since we're not turning caching on until test time, the caches_action
method call in the controller class does nothing. We need to re-add it in our test spec.
This is ugly; it doesn't test very much (except the underlying caching module, and why bother testing the framework). At least it proves to ourselves that the action is cached and the cache key is what we expect.
Now that we've got caching under control, let's check cache expiration (using a Sweeper).
First, I create a cached object, in this case, just the string 'CACHED ACTION' and then I invoke the action, and then, I hope, the cache will be expired.
It doesn't really matter what happens in the #update
method of the BrandsController
as long as it updates a Brand
object. A sweeper in Rails is a mix of Observer & controller filters, so all you need to do is "declare" it in the controller
Awesome sauce! Now our tests are red and I'm ready to implement the sweeper
And voilĂ ! We have greenness.
So what have we learned from this? The Rails source is still your best friend when exploring a sticky problem. Caching is hard, and testing caching is even harder.