I'm looking for a clean way to use JBuilder and test the json output with RSpec. The popular way for JSON testing is to implement the as_json method, and then in RSpec compare the received object with the object.to_json method. But a large reason I'm using JBuilder is that I don't want all the attributes that to_json spits out; so this breaks comparison.
Currently with JBuilder I'm having to do the following to test the RSpec results:
1) Create a Factory object: @venue
2) Create a hash inside my RSpec test that contains the "expected" JSON string back
@expected => {:id => @venue.id,:name=>@venue.name..........}
2) Compare the @expected string to the results.response.body that is returned from the JSON call.
This seems simple, except I have objects being rendered with 15+ attributes, and building the @expected hash string is cumbersome and very brittle. Is there a better way to do this?
It's a little clunkier than with say ERB, but you can use binding
and eval
to run the Jbuilder template directly. E.g. given a typical Jbuilder
template app/views/items/_item.json.jbuilder
that refers to an instance item
of the Item
model:
json.extract! item, :id, :name, :active, :created_at, :updated_at
json.url item_url(item, format: :json)
Say you have an endpoint that returns a single Item
object. In your request spec, you hit that endpoint:
get item_url(id: 1), as: :json
expect(response).to be_successful # just to be sure
To get the expected value, you can evaluate the template as follows:
item = Item.find(1) # local variable `item` needed by template
json = JbuilderTemplate.new(JbuilderHandler) # local variable `json`, ditto
template_path = 'app/views/items/_item.json.jbuilder'
binding.eval(File.read(template_path)) # run the template
# or, for better error messages:
# binding.eval(File.read(template_path), File.basename(template_path))
expected_json = json.target! # template result, as a string
Then you can compare the template output to your raw HTTP response:
expect(response.body).to eq(expected_json) # plain string comparison
Or, of course, you can parse and compare the parsed results:
actual_value = JSON.parse(response.body)
expected_value = JSON.parse(expected_json)
expect(actual_value).to eq(expected_value)
If you're going to be doing this a lot -- or if, for instance, you want to be able to compare the template result against individual elements of a returned JSON array, you might want to extract a method:
def template_result(template_path, bind)
json = JbuilderTemplate.new(JbuilderHandler)
# `bind` is passed in and doesn't include locals declared here,
# so we need to add `json` explicitly
bind.local_variable_set(:json, json)
bind.eval(File.read(template_path), File.basename(template_path))
JSON.parse(json.target!)
end
You can then do things like:
it 'sorts by name by default' do
get items_url, as: :json
expect(response).to be_successful
parsed_response = JSON.parse(response.body)
expect(parsed_response.size).to eq(Item.count)
expected_items = Item.order(:name)
expected_items.each_with_index do |item, i| # item is used by `binding`
expected_json = template_result('app/views/items/_item.json.jbuilder', binding)
expect(parsed_response[i]).to eq(expected_json)
end
end
You should be able to test your Jbuilder views with RSpec views specs. You can see the docs at https://www.relishapp.com/rspec/rspec-rails/v/2-13/docs/view-specs/view-spec.
An example spec for a file located at 'app/views/api/users/_user.json.jbuilder', could be something like this (spec/views/api/users/_user.json.jbuilder_spec.rb):
require 'spec_helper'
describe 'user rendering' do
let(:current_user) { User.new(id: 1, email: '[email protected]') }
before do
view.stub(:current_user).and_return(current_user)
end
it 'does something' do
render 'api/users/user', user: current_user
expect(rendered).to match('[email protected]')
end
end
I don't like testing the JSON API through the views, because you have to essentially mimic, in the test, the setup already done in the controller. Also, bypassing the controller, you aren't really testing the API.
In controller tests, however, you'll find that you don't get any JSON returned in the response body. The response body is empty. This is because RSpec disables view rendering in controller tests. (For better or worse.)
In order to have an RSpec controller test of your view rendered JSON API, you must add the render_views
directive at the top of your test. See this blog post (not mine), for more detailed information about using RSpec controller tests with Jbuilder.
Also, see this answer.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With