Skip to content

Any thoughts about RSpec'ing views? #130

Closed
ognevsky opened this Issue Oct 21, 2011 · 18 comments

7 participants

@ognevsky

Hello everybody,

I'd like to know about some best practices in testing Rabl–views.

Thanks a lot.

@nesquena
Owner

The way I do controller testing is pretty straightforward, I perform a request and then parse the json into a hash. I then test equality and/or patterns for different properties of the hash response. I verify the keys that are being returned, verify key values are correct, items are created as described in the response, status codes are correct, etc. It looks something like this:

describe Foo do
  before do
    # Helper I created that does a "get" and makes a response hash available
    json_get :index, :foo => "bar" 
  end

  it "returns the expected keys" do
     assert_equal ["key1", "key2"], @json_response['foo'].keys
  end

  it "sets correct value for foo" do
     assert_equal "bar", @json_response['foo']['key1']
  end
end

That type of thing. Hope that's helpful.

@ognevsky

Thanks a lot, that was really helpful!

@ognevsky ognevsky closed this Oct 21, 2011
@sabcio
sabcio commented Oct 24, 2011

Hi,
I'm using rails2.3 and rspec1.3.4 for testing. In controllers I would usually do something like:

it "should render foo"
  get :index
  content = JSON.parse(response.body)
  content.should == "something"
end

but now using rabl and checking response.body is always an empty string " ". Running the server and checking with curl or browsers renders the proper json. How does your json_get method look like?

@nesquena
Owner

I have a Rails 2.3 project and use test/unit shoulda and the json_get method really just looks something like this (ok its more complicated, but this is the relevant part):

def json_get(action, params)
  get action
  @json_response = JSON.parse(response.body)
end

I have never had a problem with RABL returning an empty response body on Rails 2,3 or Padrino. Are you sure that the response body is always blank and your action isn't somehow getting caught by a before filter or something in your tests.

@sabcio
sabcio commented Oct 24, 2011

Its a single test with a before block which creates objects and signs in the users.
The response body is always " " (with space) so when JSON.parse is called with that argument it raises A JSON text must at least contain two octets!. If I'll find a solution I will let you know.

@sabcio
sabcio commented Oct 24, 2011

The problem is with Rspec and RABL. I created an empty applications github and wrote three tests: rspec controller test, rspec view test and test/unit controller test.

rspec controller test fails (response.body = " "), rspec view test and test/unit render the correct json.

@nesquena
Owner

Hmm, I don't use rspec in any of my RABL projects, I generally use minitest, riot or shoulda. If anyone has any insights into why this fails with rspec only as far as I can tell, I would happily accept a patch. I recognize that RABL should work with Rspec tests, but I don't know enough about it to fix the issue.

@yolk
yolk commented Oct 25, 2011

Controller specs by default are run in isolation; see http://rspec.info/rails/writing/controllers.html

This means the spec won't render any templates, but returns the result of @post.to_json. Because of that your specs started failing.

The solution to testable rabl-output in your controller specs is to call integrate_views in your spec-file:

describe Foo do
  integrate_views

  before do
    # Helper I created that does a "get" and makes a response hash available
    json_get :index, :foo => "bar" 
  end

  it "returns the expected keys" do
     assert_equal ["key1", "key2"], @json_response['foo'].keys
  end

  it "sets correct value for foo" do
     assert_equal "bar", @json_response['foo']['key1']
  end
end

But beware that this will also render other templates (like erb); so be careful when adding.

@nesquena
Owner

Great, thanks for explaining that. Can someone confirm that this solves the issue?

@sabcio
sabcio commented Oct 25, 2011

I haven't thought about integrate_views. Thanks a lot, this solution works.

@nesquena
Owner

I can see why they would isolate the controller tests from the views. Is there a more appropriate place to test rabl in rspec then? I mean how would you test a builder template or any other equivalent type response? What's the appropriate place for that? View tests? or Integration tests?

@yolk
yolk commented Oct 25, 2011

I don't actually know the best way for myself; I just got started playin' with rabl. The apps I worked on all used #to_xml and friends. I always wrote kind of view spec for them on the model-level; which includes setting up fixtures to serialize.

With rapl it would maybe make sense to test them with mocks/stubs like you would test an plain old html view. But I never really got used to use view testing consequently; maybe I'll give it another try with rabl.

I don't think it would make sense to test rabls output extensivly in full integration tests; I guess this would get really slow in no time.

@wulftone

I'd prefer to isolate my rabl responses by testing only the rabl files themselves--not the controllers. I'm trying to figure out how to do this in rspec right now.... I'd be okay with throwing a real ActiveRecord object at rabl for my tests just to see if it's generating the JSON I expect. Any guidance would be awesome...! I'm not sure if rspec view specs are appropriate here, but they seem to be the most likely candidate.

EDIT: It should rely on Rails still, and load all of the models and activerecord junk.

EDIT: I figured it out.

# spec/views/households/households.json.rabl_spec.rb
require 'spec_helper'

describe 'households/households.json.rabl' do
  it "renders a json representation of a household" do
    user = User.make!(:with_relations)
    user.households << Household.make(account: user.account)
    households = []
    user.households.each do |h|
      households.push({
        id: h.id,
        name: h.name,
        errors: {}
      }.stringify_keys)
    end
    assign(:households, user.households)
    render
    JSON.parse(rendered).should eq households
  end
end

# app/views/households/households.json.rabl
collection @households
attributes :id, :name
node :errors do |m|
  m.errors.to_hash
end

If you're using the watchr gem (I use it in conjunction with spork), you'll want to modify your watchr config to include .rabl files, like so:

def run_spec(file)
  unless File.exist?(file)
    puts "#{file} does not exist"
    return
  end

  puts "Running #{file}"
  system "bundle exec rspec #{file}"
  puts
end

watch("spec/.*/*_spec.rb") do |match|
  run_spec match[0]
end

watch("app/(.*/.*).rb") do |match|
  run_spec %{spec/#{match[1]}_spec.rb}
end

# Here's the rabl watchr part
watch("app/(.*/.*).rabl") do |match|
  run_spec %{spec/#{match[1]}.rabl_spec.rb}
end
@wulftone

In an amendment to my previous post, I figured out how to test rabl templates without Rails.

# user.json.rabl_spec.rb
APP_ROOT = File.expand_path(File.join(File.dirname(__FILE__), '..', '..', '..', '..'))
$: << File.join(APP_ROOT, 'spec/support') # for my spec helpers
$: << File.join(APP_ROOT, 'config/initializers') # for rabl_init.rb
require 'my_spec_mocks'
require 'rabl'
require 'rabl_init'

# We need to fool rabl into thinking we are running rails.
# All rabl wants is the root path to help in finding templates.
# This is necessary for rabl functions like "extends" and "partial"
# that refer to other templates, but obviously this spec knows nothing
# about where they are, so we tell rabl!
class Rails
  def self.root
    APP_ROOT
  end
end

# managed to shave off about 200ms by not requiring 'active_support/core_ext'
# but I needed this:
module JSON; end

describe 'homefront/users/user.json.rabl' 
  include MySpecMocks # this is just a set of mocks I can use to fake activerecord objects
  before do
    f = File.open File.join(APP_ROOT,'app/views/homefront/users/user.json.rabl')
    @engine = ::Rabl::Engine.new(f.read, { :format => :json })
    @user = make_mock_user
    @rendered = JSON.parse @engine.render(nil, { object: @user })
  end
  context 'renders' do
    it 'first_name' do
      @rendered['first_name'].should eq @user.first_name
    end
    it 'last_name' do
      @rendered['last_name'].should eq @user.last_name
    end
    it 'email' do
      @rendered['email'].should eq @user.email
    end
    it 'relationships_as_dependent' do
      @rendered['relationships_as_dependent'].should be_present
    end
  end
end

Super fast specs!

EDIT: took out a bit of useless code from one of my experiments.

@nesquena
Owner

Very cool, isolated rabl view specs. Thanks for sharing.

@nesquena
Owner

Also for others viewing this, be sure to check out http://stackoverflow.com/questions/9576756/using-rspec-how-do-i-test-the-json-format-of-my-controller-in-rails-3-0-11 as an alternative by using render_views.

@shingara

To information with the Rabl.render method it's now simplest. I do in my spec_view ( with using json_spec gem )

require 'spec_helper'

describe 'harbors/index.rabl' do
  let(:harbor) { Fabricate(:harbor) }
  let(:harbor_json) {
    {
      name: harbor.name,
      lat: harbor.lat,
      lng: harbor.lng
    }.to_json
  }
  before do
    @rendered = Rabl.render([harbor],
                            'harbors/index',
                            :view_path => 'app/views')
  end
  it 'should have one items' do
    @rendered.should have_json_size(1)
  end

  it 'should have name information about harbor' do
    @rendered.should include_json(harbor_json)
  end
end
@didil
didil commented May 10, 2013

great I also had the 2 empty response issue, now using render_views with rspec solves the problem

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Something went wrong with that request. Please try again.