Monday, April 23, 2007

Using Singleton Method

Ruby has some good features such as you can define/override a method just for one specific object. Basically you can do this:

(in irb)


class A
end

a = A.new
b = A.new

def a.one
1
end

irb(main):007:0> a.one
=> 1

irb(main):007:0> b.one
NoMethodError: undefined method `one' for #
from (irb):11
from :0


This feature is coming in handy when you want to mock out an instance method for a specific object.

Mocha can do this as well, but for an ActiveRecord object, it looks like do something like (assuming a is an ActiveRecord object)


a.expects(:some_method).returns(something)


has unpredictable behavior. Sometimes Mocha complains actual calls is 0 even though we do call some_method in the code being tested. We suspect it's because somehow Mocha couldn't find this specific object. If you change the statement to:


A.any_instance.expects(:some_method).returns(:some_thing)


where A is the type of a.

Then it works just fine, but we are not quite comfortable with this.

And in case if we want to mock out the same method on two different objects and behaves differently, Mocha is no help. In this case, we can use Singleton method to do the mocking:


...
def a.some_method
something
end

def b.some_method
something_else
end
...


This will effectively mock out the method some_method and has no side effect. Pretty neat.

3 comments:

Unknown said...

Can you give me an example of a test where Mocha is not behaving as you would expect?

Yi Wen said...

here is an example:


a = A.create!(some_params)
b = A.create!(other_params)

...

a.expects(:some).returns(0)
b.expects(:some).returns(1)

...


A is an ActiveRecord.

If you run just this test, most likely it will be fine. If you run the whole test suite, it may or may not cause the problem. e.g. I run rake on my local machine and it works just fine, but I checked in and the build was broken because of the mocks. It'll be great if you could look into this issue. Thanks.

Yi Wen said...

Actually, this happens when the method under test uses find method to fetch the object from the DB, which makes the object in the test is not the "same" object that is used in the method under test