views:

121

answers:

2

I am using TestUnit and would like to determine if a function is called. I have a method in a class called Person which I set to be called 'before_update':

def geocode_if_location_info_changed
    if location_info_changed?
      spawn do
        res = geocode
      end
    end
  end

Then I have a unit test:

def test_geocode_if_location_info_changed
  p = create_test_person
  p.address = "11974 Thurloe Drive"
  p.city = "Baltimore"
  p.region = Region.find_by_name("Maryland")
  p.zip_code = "21093"
  lat1 = p.lat
  lng1 = p.lng

  # this should invoke the active record hook
  # after_update :geocode_if_location_info_changed
  p.save
  lat2 = p.lat
  lng2 = p.lng
  assert_not_nil lat2
  assert_not_nil lng2
  assert lat1 != lat2
  assert lng1 != lng2

  p.address = "4533 Falls Road"
  p.city = "Baltimore"
  p.region = Region.find_by_name("Maryland")
  p.zip_code = "21209"

  # this should invoke the active record hook
  # after_update :geocode_if_location_info_changed
  p.save

  lat3 = p.lat
  lng3 = p.lng
  assert_not_nil lat3
  assert_not_nil lng3
  assert lat2 != lat3
  assert lng2 != lng3
end

How can I ensure that the "geocode" method was called? This is more important for the case where I want to make sure it is not called if location information has not changed.

Thanks!

+1  A: 

What you need are mock objects (see Mockobjects and Mocks aren't stubs for more general info). RSpec has support for them, and there are other standalone libraries out there (for example, Mocha), which should help you if you don't need to switch to RSpec.

pantulis
What should I mock here? My person object is the System Under Test and according to Martin Fowler's article, mocks should be used for collaborators.
Tony
+1  A: 

Use mocha. This tests the logic of your filter:

def test_spawn_if_loc_changed
  // set up omitted
  p.save!
  p.loc = new_value
  p.expects(:spawn).times(1)
  p.save!
end

def test_no_spawn_if_no_data_changed
  // set up omitted
  p.save!
  p.other_attribute = new_value
  p.expects(:spawn).times(0)
  p.save!
end
ndp
I agree with this answer, although I think you want `p.expects(:geocode_if_location_info_changed).times(1)`. I'm OK with using mocha here. A before filter seems pretty close to a collaborator in my opinion.
Barry Hess
I was trying to test the logic of the before filter, and *spawn* is the first thing inside the if. If you just test the calling of the before filter, you are only testing that the "before_filter" was present and that rails is working... that's good, but it seemed like we're trying to get at the logic within the filter.
ndp
Good point - I can see that angle.
Barry Hess
thanks for the answer, but I would like to know if "geocode" is called inside spawn. either way, when I do p.expects(:spawn).times(1), when the program gets to spawn the first time, will it actually be skipped and not called?
Tony
and i guess another option is to do something like Person.any_instance.stubs(:geocode).returns(true)
Tony
I would probably isolate out these behaviors to make sure I got it all right:To test that geocode is called inside of *#spawn*, you can use a direct call to #geocode_if_location_info_changed: then stub *location_info_changed?* to true, and expectg mock #geocode.
ndp