Topic: speeding up tests by avoiding fixtures - mock objects instead..

im trying to speed up my rails testing by avoiding fixtures as much as possible..

after finding articles related to this, my first attempt was to convert my account_controller test (from the act_as_authenticated plugin) to avoid hitting the database through mock objects (using Mocha plugin)..

i ran the tests without my database running, and the first error comes from:

[code=account_controller_test]  def test_should_allow_signup
    assert_difference User, :count do
      create_user
      assert_response :redirect
    end
  end[/code]
ive provided the account_controller code at the bottom of this post as a reference..

any idea what i need to change in the method above and in the account_controller for mocha?





[code=account_controller]class AccountController < ApplicationController
  layout 'dashboard', :except => [:login, :signup]
 
  before_filter :login_required, :only => [:index]
 
  # say something nice, you goof!  something sweet.
  def index
    redirect_to(:action => 'signup') unless logged_in? || User.count > 0
  end

  def login
    return unless request.post?
    self.current_user = User.authenticate(params[:login], params[:password])
    if current_user
      if params[:remember_me] == "1"
        self.current_user.remember_me
        cookies[:auth_token] = { :value => self.current_user.remember_token , :expires => self.current_user.remember_token_expires_at }
      end
     
      redirect_back_or_default(:controller => '/account', :action => 'index')
      flash[:notice] = "Logged in successfully"
    end
  end

  def signup
    @user = User.new(params[:user])
    return unless request.post?
    @user.save!
    self.current_user = @user
    redirect_back_or_default(:controller => '/account', :action => 'index')
    flash[:notice] = "Thanks for signing up!"
  rescue ActiveRecord::RecordInvalid
    render :action => 'signup'
  end
 
  def logout
    self.current_user.forget_me if logged_in?
    cookies.delete :auth_token
    reset_session
    flash[:notice] = "You have been logged out."
    redirect_back_or_default(:controller => '/dashboard', :action => 'index')
  end
end[/code]

Last edited by d3wu (2007-03-04 09:01:35)

Re: speeding up tests by avoiding fixtures - mock objects instead..

What error message are you getting? And can you post more of the account_controller_test?

Railscasts - Free Ruby on Rails Screencasts

Re: speeding up tests by avoiding fixtures - mock objects instead..

sorry i forgot to mention the error message is the one i get when i dont have the database running:

[code=test_should_allow_signup - AccountControllerTest]
Exception: Bad file descriptor - connect(2)
C:/radrails/workspace/compute_stats/config/../vendor/rails/activerecord/lib/active_record/vendor/mysql.rb:111:in `initialize'
C:/radrails/workspace/compute_stats/config/../vendor/rails/activerecord/lib/active_record/vendor/mysql.rb:111:in `real_connect'
C:/radrails/workspace/compute_stats/config/../vendor/rails/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb:388:in `connect'
C:/radrails/workspace/compute_stats/config/../vendor/rails/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb:152:in `initialize'
C:/radrails/workspace/compute_stats/config/../vendor/rails/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb:82:in `mysql_connection'
C:/radrails/workspace/compute_stats/config/../vendor/rails/activerecord/lib/active_record/connection_adapters/abstract/connection_specification.rb:260:in `connection_without_query_cache='
C:/radrails/workspace/compute_stats/config/../vendor/rails/activerecord/lib/active_record/query_cache.rb:54:in `connection='
C:/radrails/workspace/compute_stats/config/../vendor/rails/activerecord/lib/active_record/connection_adapters/abstract/connection_specification.rb:228:in `retrieve_connection'
C:/radrails/workspace/compute_stats/config/../vendor/rails/activerecord/lib/active_record/connection_adapters/abstract/connection_specification.rb:78:in `connection'
C:/radrails/workspace/compute_stats/config/../vendor/rails/activerecord/lib/active_record/fixtures.rb:547:in `teardown'
C:/InstantRails/ruby/lib/ruby/1.8/test/unit/testcase.rb:77:in `run'
C:/InstantRails/ruby/lib/ruby/1.8/test/unit/testsuite.rb:32:in `run'
C:/InstantRails/ruby/lib/ruby/1.8/test/unit/testsuite.rb:31:in `run'
C:/InstantRails/ruby/lib/ruby/1.8/test/unit/testsuite.rb:32:in `run'
C:/InstantRails/ruby/lib/ruby/1.8/test/unit/testsuite.rb:31:in `run'
C:/InstantRails/ruby/lib/ruby/1.8/test/unit/ui/testrunnermediator.rb:44:in `run_suite'
C:/radrails/plugins/org.rubypeople.rdt.testunit_0.8.0.604272100PRD/ruby/RemoteTestRunner.rb:107:in `start_mediator'
C:/radrails/plugins/org.rubypeople.rdt.testunit_0.8.0.604272100PRD/ruby/RemoteTestRunner.rb:52:in `start'
C:/radrails/plugins/org.rubypeople.rdt.testunit_0.8.0.604272100PRD/ruby/RemoteTestRunner.rb:272
[/code]

here is the rest of the test code:

[code=account_controller_test.rb]
require File.dirname(__FILE__) + '/../test_helper'
require 'account_controller'

# Re-raise errors caught by the controller.
class AccountController; def rescue_action(e) raise e end; end

class AccountControllerTest < Test::Unit::TestCase
  fixtures :users

  def setup
    @controller = AccountController.new
    @request    = ActionController::TestRequest.new
    @response   = ActionController::TestResponse.new
  end

  def test_should_login_and_redirect
    post :login, :login => 'quentin', :password => 'test'
    assert session[:user]
    assert_response :redirect
  end

  def test_should_fail_login_and_not_redirect
    post :login, :login => 'quentin', :password => 'bad password'
    assert_nil session[:user]
    assert_response :success
  end

  def test_should_allow_signup
    assert_difference User, :count do
      create_user
      assert_response :redirect
    end
  end

  def test_should_require_login_on_signup
    assert_no_difference User, :count do
      create_user(:login => nil)
      assert assigns(:user).errors.on(:login)
      assert_response :success
    end
  end

  def test_should_require_password_on_signup
    assert_no_difference User, :count do
      create_user(:password => nil)
      assert assigns(:user).errors.on(:password)
      assert_response :success
    end
  end

  def test_should_require_password_confirmation_on_signup
    assert_no_difference User, :count do
      create_user(:password_confirmation => nil)
      assert assigns(:user).errors.on(:password_confirmation)
      assert_response :success
    end
  end

  def test_should_require_email_on_signup
    assert_no_difference User, :count do
      create_user(:email => nil)
      assert assigns(:user).errors.on(:email)
      assert_response :success
    end
  end

  def test_should_logout
    login_as :quentin
    get :logout
    assert_nil session[:user]
    assert_response :redirect
  end

  def test_should_remember_me
    post :login, :login => 'quentin', :password => 'test', :remember_me => "1"
    assert_not_nil @response.cookies["auth_token"]
  end

  def test_should_not_remember_me
    post :login, :login => 'quentin', :password => 'test', :remember_me => "0"
    assert_nil @response.cookies["auth_token"]
  end
 
  def test_should_delete_token_on_logout
    login_as :quentin
    get :logout
    assert_equal @response.cookies["auth_token"], []
  end

  def test_should_login_with_cookie
    users(:quentin).remember_me
    @request.cookies["auth_token"] = cookie_for(:quentin)
    get :index
    assert @controller.send(:logged_in?)
  end

  def test_should_fail_expired_cookie_login
    users(:quentin).remember_me
    users(:quentin).update_attribute :remember_token_expires_at, 5.minutes.ago
    @request.cookies["auth_token"] = cookie_for(:quentin)
    get :index
    assert !@controller.send(:logged_in?)
  end

  def test_should_fail_cookie_login
    users(:quentin).remember_me
    @request.cookies["auth_token"] = auth_token('invalid_auth_token')
    get :index
    assert !@controller.send(:logged_in?)
  end

  protected
    def create_user(options = {})
      post :signup, :user => { :login => 'quire', :email => 'quire@example.com',
        :password => 'quire', :password_confirmation => 'quire' }.merge(options)
    end
   
    def auth_token(token)
      CGI::Cookie.new('name' => 'auth_token', 'value' => token)
    end
   
    def cookie_for(user)
      auth_token users(user).remember_token
    end
end
[/code]

the fixture for users
[code=users.yml]
# Read about fixtures at http://ar.rubyonrails.org/classes/Fixtures.html
quentin:
  id: 1
  login: quentin
  email: quentin@example.com
  salt: 7e3041ebc2fc05a40c60028e2c4901a81035d3cd
  crypted_password: 00742970dc9e6319f8019fd54864d3ea740f04b1 # test
  #crypted_password: "ce2/iFrNtQ8=\n" # quentin, use only if you're using 2-way encryption
  created_at: <%= 5.days.ago.to_s :db %>
  # activated_at: <%= 5.days.ago.to_s :db %> # only if you're activating new signups
aaron:
  id: 2
  login: aaron
  email: aaron@example.com
  salt: 7e3041ebc2fc05a40c60028e2c4901a81035d3cd
  crypted_password: 00742970dc9e6319f8019fd54864d3ea740f04b1 # test
  # activation_code: aaronscode # only if you're activating new signups
  created_at: <%= 1.days.ago.to_s :db %>
[/code]


there is a similar error for each method in the account_controller_test.

if i enable the database, each of these errors go away. i was hoping to convert this whole controller test class using mock objects instead.  is this possible?

if i make the changes suggested by this article, my app will complain about not being able to find fixtures.. but i figured this would go away by getting rid of fixtures all together and using mock ojbects..

http://blog.jayfields.com/2006/06/ruby- … tests.html

Last edited by d3wu (2007-03-04 16:00:31)

Re: speeding up tests by avoiding fixtures - mock objects instead..

d3wu wrote:

if i enable the database, each of these errors go away. i was hoping to convert this whole controller test class using mock objects instead.  is this possible?

Fixtures are slow, I agree. Turning them off is a great way to improve performance in the tests. However, this doesn't mean you should turn off the database entirely.

I think trying to mock the models and ActiveRecord is a little extreme. No doubt you can do it, but I think it will introduce more problems than it will solve. If the mock behaves slightly differently then the true model this can cause a lot of tricky problems.

If you turn off fixtures (I think it's possible with a config in the test helper?) and just use empty databases you won't have to mock the models at all. You just need to create an entry in the database if you need to use it (on a test by test basis). In fact, sometimes you don't have to create a database entry, just creating the model in memory can be sufficient.

Railscasts - Free Ruby on Rails Screencasts