DRY Validation Testing and easy edge-case Records

I just pushed my latest changes and additions for the old asser_invalid_attributes to github.

  • create single valid record
  • edge-case records without fixtures
  • valid attributes to test a post or fill a form

README

Install

script/plugin install git://github.com/grosser/valid_attributes.git

Usage
  • set of valid attributes:
    valid_attributes User
  • a valid Record(new):
    valid User
  • a valid saved Record:
    create_valid User
  • an edge-case User:
    valid User, :name=>’oh noo it is too long’

Enhanced RSpec Profiling

If normal rspec profile output is not enought, try this enhanced rspec profile formatter!

Example

Groups:
5.1150300 Movie
4.9603220 Icon
1.7279670 User
1.4466160 Person
...

Single examples:
4.8707710 Icon refresh! resizes existing thumbs
0.9086340 Review releases the movie
0.5203390 Movie finds invalid
...

Install

script/plugin install script/plugin install git://github.com/grosser/rspec_enhanced_profile.git

#add to spec/spec.opts:
--format RspecEnhancedProfile:tmp/profile.txt

Profiling RSpec

Small option, but took me 2 hours to find :/
This will automatically generate reports for your test-runs.

It only shows single examples, which is sad since i would also want to know which controller_specs take the longest…

Output:

Top 10 slowest examples:
0.5215740 User downgrades to a person
0.4326950 User finds invalid
0.1914630 User remembering unsets remember token
0.1218360 User is valid
0.0903790 Festival is valid

Install:

#spec/spec.opts
--format profile:spec/profile.txt

Small Spec Helpers

So small but so powerful, i just wanted to share my little time savers, and hope you share your most essential helpers in the comments.

(I use @item as an alias for the current object in all my tests, to make generic tests less painful.)

Small example:

it "renders feedback" do
  expects_find
  get :feedback , :id => @item.to_param
  response.should render_template('edit')
end

Code:

#spec/spec_helper.rb
def mock_create(item,success,para=nil)
  if para
    item.class.expects(:new).with(para).returns item
  else
    item.class.expects(:new).returns item
  end
  item.expects(:save).returns success
end

def mock_update(item,success)
  expects_find(item)
  item.expects(:save).returns success
end

def expects_find(item=nil)
  item ||= @item
  item.class.expects(:find).with(item.to_param).returns(item)
end

Separate Rights Management from Controllers

Update: new controller methods inspired by Nicolás Sanguinetti

All to often Controllers verify if a certain user can say “create or edit a resource”, which at first seems to be a natural place for this kind of logic, but alas it is not!

When building your views you need access to the same knowledge, if the user can edit this movie, show the edit link.

Is it mine?

At first i solved this with a simple is_mine?(obj) on the user, and let the user decide if he can edit a record.

def is_mine?(obj)
  case obj
    when Movie then obj.id == id
    when Participation then obj.movie.id == id
   .....
end

Can i read/write it ?

But this still left too much logic for my helpers and controllers, so i added another bit: read/write, where a new User symbolizes no user, so user.can_write?(obj) returns false if it is a new_record.

  def can_read?(obj)
    raise "new objects cannot be read" if obj.new_record? #new_records can only be created, never viewed...
    case obj
      when Address,Order,OrderItem #not readable by others/public
        is_mine?(obj) #i can read an order/address if it is mine
      else not new_record?#only a logged in user can read something...
    end
  end

  def can_write?(obj)
    ...
  end

Controller-logic be gone!

Now in my controller i use can_read for show, can_write for edit/update/create/new/destroy, which i inherit on all controllers.

before_filter :can_read, :only => [:show]
before_filter :can_write, :only => [:destroy,:update,:edit,:new,:create]

def can_write
  access_denied unless can_write?(requested_object)
end

def can_write?(object)
  user = current_user || User.new
  user.can_write?(object)
end
helper_method :can_write?

The magic starts in requested_object: Create a new object of the current controller class, or find it by id. This can be a bit tricky with nested resources, but can be as simple as (see below) with make_resourceful.

  def requested_object
    case params[:action]
      when 'new','create' then build_object
      else current_object
    end
  end

Clean Tests

Testing, once cumbersome “get :new, response.should”-madness now looks like this, which is orders of magnitude faster, easier to write and more fun to read.

  it "reads/writes movies" do
    #changeable by owner
    can_read_and_write(@owner,@movie)
    can_read_not_write(@other,@movie)
    can_write(@owner,Movie.new)
    cannot_read_and_write(@no_user,@movie)
    cannot_write(@no_user,Movie.new)
  end
...
  def can_read(user,item)
    user.can_read?(item).should be_true
  end
...