Hashie and Rspec - The Problem:
Hashie is a neat little Ruby gem that extends Hash and gives object-like access and functionality to hashes. Classes can extend from Hashie and add other functionality as needed. It is especially useful when marshaling JSON or XML data from a service layer into your business models.
While Hashie is very useful, we have to be careful using this gem with ActionController Rspec tests. When creating the
assigns hash used in controller tests, Rspec creates a
HashWithIndifferentAccess, which is dangerous with objects that act like Hash (such as Hashie objects). Rspec ends up converting user-defined objects that inherit from Hashie into
HashWithIndifferentAccess objects, causing us to lose any data that exists outside of the backing hash.
Lets imagine our Rails application talks to an Employee API exposing JSON data. We have already implemented the service layer that makes the API call and returns Employee objects that extend Hashie. In the EmployeeController we make the service call and assign the resulting Employee object.
The Tests (Where the conflict occurs):
We should be able to write some simple specs to test the controller, specifically, that the result of the service call is stored in the correct variable passed into the view and that we assign whatever extra processing we need to do outside of the service layer into “foo.”
You would assume this test would pass right? Nope! Fail!
The output of both assertions would be:
As you can see, Rails has converted our Employee object into a HashWithIndifferentAccess! How dare you Rails?!
Why would Rails do this?
Well, don’t be too quick to point the finger, this is actually a combination of Rspec and Rails (ActiveSupport). Rspec is trying to make it easier for you to use the ActionController
view_assigns hash by converting it into a
HashWithIndifferentAccess. Diving into
ActionDispatch::TestProcess we find the method definition for
The easy access to the
view_assigns hash is great, but looking deeper into the internals of the
with_indifferent_access method we find that while creating the
HashWithIndifferentAccess, any assigns variable that is a
Hash is converted into a
HashWithIndifferentAccess! Since Hashie classes return true when asked if they are a
Hash, they also get converted, therefore, losing their original object identity. To me ActiveSupport is stepping over the line here. Yes, give us easy access to the assigns hash, but don’t mess with the actual values of that hash unless I tell you to.
First, this problem is unique to Rspec tests - production code does not have this same problem. One solution would be to monkey patch
ActiveSupport::HashWithIndifferentAccess. But, since I try to stay away from monkey patching whenever possible, the quick solution is just to not use
assigns when your object under test is a Hash and you care about it not being converted to a
HashWithIndifferentAccess for testing purposes.
Instead, use the controller’s
view_assigns hash directly to avoid the conversion to
In the end, its an easy workaround for a slightly annoying “feature” of Rspec.