SignOut: Part 3
In addition to the REST refactoring for SignOut, we thought we’d cover a few more changes we’d suggest.
The biggest thing which jumped out at me was that SignOut has no tests. If you’ve never written a test in rails all the different tools and frameworks available can make it seem quit daunting. It’s often hard to tell if you should you use rspec, heckle, autotest, rcov and where to start. So in this article we’ll take the set_away action from EmployeeController and write a few tests for it, using just the stuff you get for free with rails.
The action as it stands has some reasonably involved code for handling decoding time_back from the request parameters:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
def set_away @employee = Employee.find(params[:id]) if params[:date][:meridian] == "PM" if params[:date][:hour].to_i == 12 hour = params[:date][:hour] else hour = (params[:date][:hour].to_i + 12).to_s end else if params[:date][:hour].to_i == 12 hour = "0" else hour = params[:date][:hour] end end time_back = Time.mktime(params[:date][:year], params[:date][:month], params[:date][:day], hour, params[:date][:minute] ) @employee.update_attributes(:reason => params[:employee][:reason], :time_out => Time.now, :time_in => time_back) end |
So there are four distinct cases that we need to ensure are tested:
- A Meridian of “PM” and an hour of 12
- A Meridian of “PM” and some other hour
- A Meridian of “AM” and an hour of 12
- A Meridian of “AM” and some other hour
First up we need some fixtures data for an employee, lets pretend I work there and create a test fixture for koz:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
koz: id: 1 firstname: Michael lastname: Koziarski company_email: koz@example.com personal_email: michael@koziarski.com extension: 665 cellphone: +64 21 555 1337 homephone: +64 4 555 1337 time_out: time_in: reason: created_on: <%= 5.days.ago.to_date.to_s(:db) %> modified_on: <%= Date.today.to_s(:db) %> initials: MAK |
Now the tests in EmployeeControllerTest:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
class EmployeeControllerTest < Test::Unit::TestCase def setup @controller = EmployeeController.new @request = ActionController::TestRequest.new @response = ActionController::TestResponse.new end # Ensure that the employees table gets reloaded as needed fixtures :employees # we can DRY up the test data by providing a default date #and then just overriding what we need def default_date {:meridian=>"PM", :year=>now.year, :month=>now.month, :day=>now.day, :minute=>"0", :hour=>"12"} end # Test the first case, PM and 12 def test_set_away_for_pm_and_12_oclock # the default data matches this test case, post :set_away :id=>employees(:koz).id, :date=>default_date # ensure no errors ocurred assert_response :success # reload our fixture to match the database changes koz = employees(:koz).reload # make sure the time_back attribute has been set assert time_back = koz.time_back assert_equal 0, time_back.minute # 12PM is 12 midday, so the hour should be 12 assert_equal 12, time_back.hour # We provided a reason here, make sure the controller has set it assert_equal "Because", koz.reason end end |
We could continue to write tests like that, but instead we can get a little clever and let ruby write the tests for us:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
def default_date {:meridian=>"PM", :year=>now.year, :month=>now.month, :day=>now.day, :minute=>"0", :hour=>"12"} end TEST_SCENARIOS= {0=>{:hour=>"12", :meridian=>"AM"}, 5=>{:hour=>"5", :meridian=>"AM"}, 18=>{:hour=>"6", :meridian=>"PM"}, 12=>{:hour=>"12", :meridian=>"PM"}} TEST_SCENARIOS.each do |hour, hash_overide| # Make sure we give our tests useful names, otherwise it's hard to tell what failed name = "test_with_hour_#{hash_overide[:hour]}_and_meridian_#{hash_overide[:meridian]}" define_method(name) do post :set_away :id=>employees(:koz).id, :date=>default_date.merge(hash_override) # ensure no errors ocurred assert_response :success # reload our fixture to match the database changes koz = employees(:koz).reload assert time_back = koz.time_back assert_equal 0, time_back.minute # Make sure the hour matches our expected hour assert_equal hour, time_back.hour end end end |
Now you can refactor the set_away action without having to worry about introducing new date related bugs.

