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