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 => thread
You need to do this even though (or rather: because of) the belongs_to association that is already in place.