Hoopla!

now with extra whiz-bang!

Hoopla!

Time your Rails tests

October 13, 2007 · 3 comments

As a project gets bigger the tests take longer. There’s no getting around that. So unless you want to abandon testing entirely it might help to see who’s eating all those cpu cycles.

in test/test_helper.rb:

class Test::Unit::TestCase

  def run_with_timing(*args, &block)
    @timer = Time.now
    run_without_timing(*args, &block)
    puts "#{Time.now - @timer} - #{self}" 
  end
  alias_method_chain :run, :timing if ENV['TIMER']

end
And run it like so:

$ rake TIMER=true
# or, to see the 10 slowest tests:
$ rake test:units TIMER=true 2>/dev/null | grep " - " | sort -r | head -n 10
And if you really want to air the dirty laundry:

$ grep -R 'def test_truth' test/ | grep -v .svn

→ 3 comments Tags:

Rails Integration Testing - How to learn

July 30, 2006 · 0 comments

I decided to try filling up my ./test/integration folder today because it was empty and I felt I must have been missing out. I can’t stand the idea that all the other Rails devs are enjoying some feature I haven’t found out about yet.

Integration tests seem pretty handy because functional tests are pretty basic – effectively unit tests for controllers rather than tests of real interactivity. So I figured I’d google around a bit to discover this integration testing thing and this is what I found:

(which currently doesn’t have a web server so I had to grab the page from Google’s cache).

That’s it. Those three meager pages are all the info I could find on Rails integration testing. In fact, I have reason to believe that the Rails Recipes book just re-used the code from Jamis Buck’s post – so even they don’t have any more for us.

Well, that’s not acceptable. I’m trying to follow a particular user’s experience through my app and I want to get this working without having to resort to SeleniumOnRails or some other browser-emulator.

My code so far

I’ve got a particular user type `Athlete` that logs in and checks their profile. They update a couple pieces of information and we check that the info was saved. Should be easy. (note: to understand this code check out Jamis’s article – you might need to look in the Google Cache for it)

class AthleteSessionTest < ActionController::IntegrationTest

fixtures  :users,
          :athlete_contacts,
          :coach_contacts,
          :junior_colleges,
          :combines
def test_randy_visits
  randy = new_session_as('Cliffton Player')
  randy.goes_to_profile_edit
  randy.updates_profile_with :first => 'Randy', :last => 'BadAsserson'
  randy.
end
private
module AthleteSessionDSL
def goes_to(place)
  get place
end
def goes_to_login
  get '/auth/login'
  assert_response :success
  assert_template 'auth/login'
end
def goes_to_profile_edit
  get '/athletes/profile_edit'
  assert_response :success
  assert_template 'athletes/profile_edit'
end
def logs_in_as(athlete)
  @current_user = users(athlete)
  post 'auth/login', :user => {:email => @current_user.email, :password => @current_user.password }
  assert_response :redirect
  follow_redirect!
  assert_template 'athletes/profile'
end
def updates_profile_with(contact)
  post 'athletes/update_contact', :contact => contact,
                                  :athlete_id => @current_user.id
  assert_flash_equal 'This profile has been updated with new information', :success
  assert_response :redirect
  follow_redirect!
  assert_template 'athletes/profile'
  contact.each { |k,v| assert_equal v, @current_user.profile.attributes[k.to_s] }
  end
end
end
def new_session
  open_session do |sess|
    sess.extend(AthleteSessionDSL)
    yield sess if block_given?
  end
end
def new_session_as(person)
  new_session do |sess|
    sess.goes_to_login
    sess.logs_in_as(person)
    yield sess if block_given?
  end
end
end

The Problem

So this is fine. So far it’s working great. The problem is that I’ve set it up so that everything my user does is part of my AthleteSessionDSL module in it’s own method. Everything `randy` does has to be a method within the `AthleteSessionDSL` module.

I wanted to try some non-modular navigation to give Randy a bit more of a free spirit. I thought I’d see if I could take some of the functionality out of the module and do it directly:

  def test_randy_visits
    randy = new_session_as('Cliffton Player')
    randy.goes_to_profile_edit
    randy.updates_profile_with :first => 'Randy', :last => 'BadAsserson'
    # trying some direct session navigation outside of the randy session object
    get 'athletes/profile'
    assert_response :success
    assert_template 'athletes/profile'
  end

What happened? Well, since `randy` is really just an instance of a Rails session with some handy methods attached to it I immediately left the session when I stopped using `randy`. My request to get the “athletes/profile” page resulted in a redirect to my login page because that method is authentication protected.

Well, what’s the solution? Do I have to make a million methods for everything I want Randy to do?

The Solution

It turns out to be quite simple. The methods we have been using like `assert_response` and `get` (and all the rest) are, in fact, methods of the session. The `open_session` call in the `new_session` module creates a session object with all the standard methods on it.

So not only can we group functionality into methods and roll those into included modules – we can also call things directly:

  def test_randy_visits
    randy = new_session_as('Cliffton Player')
    randy.goes_to_profile_edit
    randy.updates_profile_with :first => 'Randy', :last => 'BadAsserson'
    randy.get '/athletes/profile'
    randy.assert_response :success
    randy.assert_template 'athletes/profile'
  end

→ 0 comments Tags: