IdolHands.com :: Days in the Life of an Alpha Geek
I ran into a gotcha with my RSpec test for a controller action that returns JSON output. This was one of those cases where a test that always passed suddenly and inexplicably stopped working... Which means that it was just blind luck that made it pass in the first place.
The controller method is very simple:
def index
render :json => FlashAsset.all.map{|a| {:filename => a.name, :url => a.media.url} }
end
And the corresponding test:
describe 'CRUD GET index' do
before do
FlashAsset.destroy_all
@f1 = FlashAsset.make(:filename => "flash_1.swf")
@f2 = FlashAsset.make(:filename => "flash_2.swf")
end
it 'returns a JSON index' do
get :index
response.body.should == [@f1, @f2].map{|f| f.to_json(:only => [:filename, :url])}.to_json
end
end
When this test failed, here was the result:
'FlashAssetsController CRUD GET index returns a JSON index' FAILED
expected: "[{\"filename\": \"flash_1.swf\", \"url\": \"/flash/1/original/flash_1.swf\"},
{\"filename\": \"flash_2.swf\", \"url\": \"/flash/2/original/flash_2.swf\"}]",
got: "[{\"url\": \"/flash/1/original/flash_1.swf\", \"filename\": \"flash_1.swf\"},
{\"url\": \"/flash/2/original/flash_2.swf\", \"filename\": \"flash_2.swf\"}]" (using ==)
That using == bit was the clue. Notice how logically the contents of the expected and actual results are the same, but the order of the keys is different? JSON is essentially a specially structured string, but a string nonetheless. In order to test this effectively, the JSON has to be interpreted and decoded first.
Once the JSON is decoded, the Array class' comparison operator can handle the rest:
it 'returns a JSON index' do
get :index
_expected = [@f1, @f2].map{|f| f.to_json(:only => [:filename, :url])}.to_json
# Can't count on the order of the params in the returned JSON...
ActiveSupport::JSON.decode(response.body) == ActiveSupport::JSON.decode(_expected)
end
end