After a couple of months away from it, I’ve picked up my Ruby on Rails project again. The first thing I wanted to do was make an honest developer of myself and make my build pass–up until now I’d been allowing myself to think of my project as experimental and was cutting a lot of corners just to figure things out.
First, let me state that the unit testing that’s built into Ruby and into Rails is exceptional. Without having to even think about it, the default build target will run your unit tests, functional tests, and insert and tear down data fixtures lickety split. After going to a session by Kevin Clark on unit testing in Rails at SD West, I was even farther ahead.
My first problem, though, was the point at which I wanted to do something slightly unusual like check for the presence or absence of a variable in the session. Now, I’ve been doing most of my development offline in coffee shops or on the bus, so I didn’t have access to excellent Rails unit testing tutorials online. I was on my own.
I did, however, have this skeletal unit test from the SD West session:
require File.dirname(__FILE__) + '/../test_helper'
require 'posts_controller'
class PostsController; def rescue_action(e) raise e end; end
class PostsControllerTest < Test::Unit::TestCase
fixtures :posts
def setup
@controller = PostsController.new
@request = ActionController::TestRequest.new
@response = ActionController::TestResponse.new
end
def test_show
get :show, :id => posts(:ruby).id
assert_response :success
assert_equal posts(:ruby), assigns(:post) # assigns looks at instance variables inside the controller
assert_match( /#{posts(:ruby).title}/, @response.body)
end
def test_create
startcount = Post.count
post :create, :post => {:title => 'Hi SD West', :body => 'Here I am'}
endcount = Post.count
assert_equal 1, endcount - startcount
assert_redirected_to :action => 'show', :id => assigns(:post)
end
end
Here, the assigns hash will contain any instance variables I set in the controller. I figured, hell, there must be a session hash, too. So I tried this:
require File.dirname(__FILE__) + '/../test_helper'
require 'home_controller'
class HomeController; def rescue_action(e) raise e end; end
class HomeControllerTest < Test::Unit::TestCase
fixtures :registered_users
def setup
@controller = HomeController.new
@request = ActionController::TestRequest.new
@response = ActionController::TestResponse.new
@first_user = registered_users(:one)
end
def test_list
#should fail if there's no registered user in the session
get :list
assert_response :success
assert_template 'login'
assert_nil session[:registered_user]
end
end
Nope, didn't work. After much fumbling and messing around, I finally discovered that the session lives in that mysterious @response variable, as below. And this whole exercise has exposed to me my greatest frustration with Ruby (and other dynamic languages like Python, PHP, etc): if it's not strongly typed, then your IDE can't do much to help you. If I had been working in Java, I could have typed "super.<ctrl -space>" or "@response.<ctrl -space>" or etc. in Eclipse and seen immediately what the available methods and fields were. I didn't realize before just exactly how reliant I'd become on that before I started using more dynamic languages this last few years. I guess you can't have your cake and eat it, too.
For posterity, here is the real solution:
require File.dirname(__FILE__) + '/../test_helper'
require 'home_controller'
class HomeController; def rescue_action(e) raise e end; end
class HomeControllerTest < Test::Unit::TestCase
fixtures :registered_users
def setup
@controller = HomeController.new
@request = ActionController::TestRequest.new
@response = ActionController::TestResponse.new
@first_user = registered_users(:one)
end
def test_list
#should fail if there's no registered user in the session
get :list
assert_response :success
assert_template 'login'
assert_nil @response.session[:registered_user]
end
end