New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Any thoughts about RSpec'ing views? #130

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

Comments

Projects
None yet
7 participants
@ognevsky
Contributor

ognevsky commented Oct 21, 2011

Hello everybody,

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

Thanks a lot.

@nesquena

This comment has been minimized.

Show comment
Hide comment
@nesquena

nesquena Oct 21, 2011

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.

Owner

nesquena commented Oct 21, 2011

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

This comment has been minimized.

Show comment
Hide comment
@ognevsky

ognevsky Oct 21, 2011

Contributor

Thanks a lot, that was really helpful!

Contributor

ognevsky commented Oct 21, 2011

Thanks a lot, that was really helpful!

@ognevsky ognevsky closed this Oct 21, 2011

@sabcio

This comment has been minimized.

Show comment
Hide comment
@sabcio

sabcio 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?

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

This comment has been minimized.

Show comment
Hide comment
@nesquena

nesquena Oct 24, 2011

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.

Owner

nesquena commented Oct 24, 2011

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

This comment has been minimized.

Show comment
Hide comment
@sabcio

sabcio 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 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

This comment has been minimized.

Show comment
Hide comment
@sabcio

sabcio 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.

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

This comment has been minimized.

Show comment
Hide comment
@nesquena

nesquena Oct 24, 2011

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.

Owner

nesquena commented Oct 24, 2011

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

This comment has been minimized.

Show comment
Hide comment
@yolk

yolk 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.

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

This comment has been minimized.

Show comment
Hide comment
@nesquena

nesquena Oct 25, 2011

Owner

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

Owner

nesquena commented Oct 25, 2011

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

@sabcio

This comment has been minimized.

Show comment
Hide comment
@sabcio

sabcio Oct 25, 2011

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

sabcio commented Oct 25, 2011

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

@nesquena

This comment has been minimized.

Show comment
Hide comment
@nesquena

nesquena Oct 25, 2011

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?

Owner

nesquena commented Oct 25, 2011

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

This comment has been minimized.

Show comment
Hide comment
@yolk

yolk 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.

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

This comment has been minimized.

Show comment
Hide comment
@wulftone

wulftone Dec 22, 2011

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 commented Dec 22, 2011

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

This comment has been minimized.

Show comment
Hide comment
@wulftone

wulftone Feb 26, 2012

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.

wulftone commented Feb 26, 2012

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

This comment has been minimized.

Show comment
Hide comment
@nesquena

nesquena Feb 26, 2012

Owner

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

Owner

nesquena commented Feb 26, 2012

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

@nesquena

This comment has been minimized.

Show comment
Hide comment
@nesquena

nesquena Mar 27, 2012

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.

Owner

nesquena commented Mar 27, 2012

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

This comment has been minimized.

Show comment
Hide comment
@shingara

shingara May 25, 2012

Contributor

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
Contributor

shingara commented May 25, 2012

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

This comment has been minimized.

Show comment
Hide comment
@didil

didil May 10, 2013

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

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