1

I'm having the worst time rendering a .json.erb file from my controller while being able to test it with RSpec. I have api_docs/index.json.erb and the following controller:

class ApiDocsController < ApplicationController
  respond_to :json

  def index
    render file: 'api_docs/index.json.erb', content_type: 'application/json'
  end
end

The explicit render file line seems unnecessary, but if I don't do that or render template: 'api_docs/index.json.erb', then I get an error about "Missing template api_docs/index". Likewise if I do have to pass the file name, it sucks even more that I have to give the exact directory--Rails should know that my ApiDocsController templates live in the api_docs directory.

If I have render file or render template, then I can visit the page and get the JSON contents of my index.json.erb file, as expected. However, this RSpec test fails:

let(:get_index) { ->{ get :index } }

...

describe 'JSON response' do
  subject {
    get_index.call
    JSON.parse(response.body)
  }

  it 'includes the API version' do
    subject['apiVersion'].should_not be_nil
  end
end

It fails on the JSON.parse(response.body) line and if I raise response.body, it's an empty string. If I do render json: {'apiVersion' => '1.0'}.to_json in the controller, then the test passes just fine.

So, how can I always render the JSON template when I go to /api_docs (without having to put .json at the end of the URL), and in a way that works both in the browser and in my RSpec test? And can I render the template without having to have some long render call in which I pass the full path of the view?

Sarah Vessels
  • 30,930
  • 33
  • 155
  • 222
  • I wonder if this might not answer your question: http://stackoverflow.com/questions/10681816/render-json-instead-of-html-as-default – yekta Sep 04 '13 at 18:44
  • I found that a minute ago and it helped: I can go to /api_docs instead of /api_docs.json and see the JSON in the browser, but the RSpec test still fails because `response.body` is an empty string. – Sarah Vessels Sep 04 '13 at 18:46
  • [This RSpec issue](https://github.com/rspec/rspec-rails/issues/477) might be relevant. – Sarah Vessels Sep 04 '13 at 18:51

2 Answers2

0

Actually since you're already using respond_to :json in your controller you can use just a render method to choose your template and, as you probably know, if the template have the same name of the controller method you should be able to suppress the whole render method.

If you just remove the render line, what's the result?

Miguelgraz
  • 4,376
  • 1
  • 21
  • 16
  • Going to /api_docs results in `Missing template api_docs/index`. I have to go to /api_docs.json. – Sarah Vessels Sep 04 '13 at 18:40
  • Oh, I missed your mention that you need to render a json template too, sorry. Well then you should be able to say something like `render 'api_docs/index.json'` or `render 'index.json'` – Miguelgraz Sep 04 '13 at 18:43
  • It's entirely a JSON template, actually: I never want to render HTML from this action. – Sarah Vessels Sep 04 '13 at 18:44
  • That answer is kind of ugly because it's still specifying the full path in the `render` call, which seems unnecessary. My template file isn't a partial, and definitely rendering it as `template` or `file` worked in the browser, but the RSpec test gets an empty JSON response. – Sarah Vessels Sep 04 '13 at 18:49
  • Did you try to remove the .erb of the end of the template? I know this can sound weird but I believe you don't need it and it can influence the RSpec test, since you have a "html with a json inside" which probably doesn't work properly with `JSON.parse` – Miguelgraz Sep 04 '13 at 18:54
  • Just tried that, so I had `index.json`. The JSON still rendered correctly in the browser, but the RSpec test still fails with `JSON::ParserError: A JSON text must at least contain two octets!` because the `response.body` is blank. – Sarah Vessels Sep 04 '13 at 18:55
  • Ok so it seems that the controller <> template relationship is working properly, could it be something with the way the spec is asserting the response? What's the return of a `raise response.to_yaml`? – Miguelgraz Sep 04 '13 at 19:00
  • I solved my problem with the test--see [my answer](http://stackoverflow.com/a/18621427/38743) above. It was indeed an RSpec thing: I had to add a `render_views` call in my test. – Sarah Vessels Sep 04 '13 at 19:02
  • Glad to know you found it out =) – Miguelgraz Sep 04 '13 at 19:03
0

Part of my solution was based on this answer to another question: adding defaults: {format: :json} to my routes file lets me go to /api_docs and see the JSON when the action is just def index ; end with no render. The RSpec test still fails though. The full line from my routes file: resources :api_docs, only: [:index], defaults: {format: :json}.

Thanks to this guy with the same problem and his gist, I added render_views to my describe block and got my test to pass:

describe ApiDocsController do
  render_views

  ...

  let(:get_index) { ->{ get :index } }

  describe 'JSON response' do
    subject {
      get_index.call
      JSON.parse(response.body)
    }

    it 'includes the API version' do
      subject['apiVersion'].should_not be_nil
    end
  end
Community
  • 1
  • 1
Sarah Vessels
  • 30,930
  • 33
  • 155
  • 222