Why stubbing on associated records does not always work as expected
Be careful when stubbing out attributes on records that are defined by associations. Nothing is as it seems to be.
The associated record has its own universe of things; when delegating calls to it, you ca not stub methods on the associated record and expect them to be around. That is a general issue with this pattern/approach.
What's happening?
Consider these classes:
class Post < ActiveRecord::Base
belongs_to :thread
def thread_title
thread.title
end
end
class Thread < ActiveRecord::Base
has_many :posts
end
Now look at this spec:
thread = Thread.make
post = Post.make :thread => thread
thread.stub :title => 'Hello Universe'
post.thread_title.should == 'Hello Universe'
Unfortunately, this spec will fail as post.thread_title is nil.
Why? Because post.thread is not actually a Thread even though looking at its class makes you believe so:
post.thread.class
=> Thread
Its real class is ActiveRecord::Associations::BelongsToAssociation which is sometimes exposed, for example when calling undefined methods:
post.thread.foobar
NoMethodError Exception: undefined method `foobar' for #<ActiveRecord::Associations::BelongsToAssociation:0xc7d99a0>
This means that you actually stubbed on the BelongsToAssociation object -- those stubs will be gone when our post accesses its thread.\
That also applies when you stub on post.thread instead of thread in the spec; they both have the same object_id.
When debugging, you will also see that post.thread.title is in fact "Hello Universe" while post.thread_title is nil.
How to fix it
Both these approaches work and you are probably doing one of them for most of your specs anyway:
-
Stub the method that uses the associated record.
-
Stub the association explicitly:
post.stub :thread => threadYou need to do this even though (or rather: because of) the belongs_to association that is already in place.