Faking the funk: “Stub” authentication in a Rails Rspec Story

Posted by Jarrod on August 22, 2008

Last week a ran into a bit of an issue trying to write Rspec Stories for the app I am working on. The app in question depends on a remote service based authentication system. Creating “test” users on a remote user authentication service already running as production for other apps was not really an option. Getting around this proved to be problematic as Rspec Stories entire point is to test the “full stack”.

Seeing I had no alternative and really just needed to sidestep authentication here, my first instinct was to try to directly set a user id in a session object:

Story "a fantastic story", "blah", :type => RailsStory do
  Scenario "a user does stuff" do
    Given "logged in as a user 'someone'" do
      @user = User.find_by_login('someone')
      session[:user] = @user.id
    end
    Then "all my tests pass and we live happily ever after" do
      get "/users/#{@user.id}"
      response.should be_success # login is required here... FAIL!
    end
  end
end

This does not work as I had guessed. It turns out that the session object you have access to within an Rspec Story is NOT the same object as your actual controllers will look to when you run your story. I am sure there is some hackity way of prying open the Rails stack and modifying the REAL session object, but going down that road just didn’t feel right.

My next instinct was to pull in Rspec’s wonderful stubbing/mocking framework and just stub out all the authentication. Bringing mocking into Stories was as easy putting this in my story/helper.rb file

require 'spec/mocks'

We are using a heavily modified version of the restful_authentication plugin, so I just needed to figure out a way to stub #login_required to always return true and #current_user to always return whichever user we want to be currently “logged in”. The problem is that in a Story context we are dealing with potentially ALL controllers… and there really doesn’t seem to be a way to access instances of individual controllers until AFTER a request has been made. My next thought was to stub such that all future instances of ApplicationController would contain the stubbed version of the authentication methods. Im sure this is possible to do, but it also seemed incredibly messy.

This got me thinking: “why am i searching for these complex solutions… lets just do this the most simple way possible”…

In the context of restful_authentication all that needs to happen for a user to be “logged_in” is for the user’s ID to be set in a valid session. My solution for getting it there? Monkey patch in a fake login method that can be called within the story:

class AccountsController < ApplicationController
  def fake_login
    set_session_for_story(params[:login])
    redirect_to "/users/#{current_user.login}"
  end
end
class ApplicationController
  def set_session_for_story(login)
    self.current_user = User.find_by_login(login)
  end
end

Story "a fantastic story", "blah", :type => RailsStory do
  Scenario "a user does stuff" do
    Given "logged in as a user 'someone'" do
      create_a_test_user(:login => 'someone')
      post "/sessions/fake_login", :login => 'someone'
      response.should be_redirect
    end
    Then "all my tests pass and we live happily ever after" do
      get "/users/#{@user.id}"
      response.should be_success # login is required here... SUCCESS!
    end
  end
end

This is the simplest way I could come up with for causing the Rails app itself to set the proper user into the session rather than trying to peel back the layers of the app running within the story and manipulate the correct session object directly or trying to deal with stubbing/mocking instances of objects that hadn’t yet been created.

I then took this little bit of hackity and moved it off into another file (/stories/fake_login.rb) so I could then just require it in any story where I needed an authenticated user.

Headed to Portland!

Posted by admin on May 22, 2008

So its finally official… I am headed out to RailsConf in Portland, OR next week!

Fun with Ruby blocks, modules, class inheritance, and “super”

Posted by admin on April 14, 2008

The situation: A class, it’s descendant, and a module included in the parent class all have a method of the same name. Calling super from the child’s method causes a call to the parent class and not the module…

module Something
  def foo(&block)
    puts "Hello Module,"
    yield
    puts "Goodbye Module... it was nice knowing you. "
  end
end

class ParentFun
  include Something

  def foo(&block)
    puts "Hello Parent,"
    yield
    puts "Goodbye Parent.. it was nice knowing you"
  end
end

class ChildFun < ParentFun
  def foo
    super do
      puts "I am a Child."
    end
  end
end

ChildFun.new.foo #=>
# Hello Parent,
# I am a Child.
# Goodbye Parent.. it was nice knowing you

Now put the module include in the Child class instead of Parent.

module Something
  def foo(&block)
    puts "Hello Module,"
    yield
    puts "Goodbye Module... it was nice knowing you. "
  end
end

class ParentFun
  def foo(&block)
    puts "Hello Parent,"
    yield
    puts "Goodbye Parent.. it was nice knowing you"
  end
end

class ChildFun < ParentFun
  include Something

  def foo
    super do
      puts "I am a Child."
    end
  end
end

ChildFun.new.foo #=>
# Hello Module,
# I am a Child.
# Goodbye Module... it was nice knowing you.

Interesting.

Managing branches in SVN

Posted by admin on September 01, 2007

  • Requirements and Dependencies

    1. svnmerge.py - a python script that helps you keep your branches in synch with the trunk. The script and full documentation are available at http://www.orcaware.com/svn/wiki/Svnmerge.py
    2. define a bash variable of the svn path ($TIS_SVN_URL in this case)
  • Creating the branch

    1. stop your development server
    2. commit any outstanding changes to the trunk
    3. copy the trunk to a branch
      svn copy $TIS_SVN_URL/trunk $TIS_SVN_URL/branches/branch-name
    4. switch over to that branch
      svn switch $TIS_SVN_URL/branches/branch-name
    5. initialize the branch
      svnmerge.py init
    6. commit:
      svn commit -F svnmerge-commit-message.txt
  • Working with the branch

    1. stop your development server
    2. occasionally perform svnmerge.py merge to keep in sync with the trunk
  • Merging branch back into the trunk

    1. stop your development server
    2. with a clean branch switch over to the trunk:
      svn switch $TIS_SVN_URL/trunk
    3. check properties on trunk for any merge stuff:
      svn proplist -v .
    4. Initialize the merge tracking support on the trunk, related to the given branch, using:
      svnmerge.py init $TIS_SVN_URL/branches/alpha02-dashboard
    5. commit:
      svn commit -F svnmerge-commit-message.txt
    6. update
    7. merge:
      svnmerge.py merge --bidirectional -S $TIS_SVN_URL/branches/alpha02-dashboard > merge_log.txt
    8. check merge log for any conflicts
    9. resolve any conflicts
    10. rake test
    11. commit:
      svn commit -F svnmerge-commit-message.txt
    12. svnmerge.py uninit -S $TIS_SVN_URL/branches/alpha02-dashboard
    13. commit:
      svn commit -F svnmerge-commit-message.txt
    14. svn rm -m ‘removing branch’ $TIS_SVN_URL/branches/alpha02-dashboard