Stub_model() and ActiveRecord Associations

Hi all,

I have what I thought was quite a simple requirement but something to
do with the way ActiveRecord’s associations work is making it quite
puzzling.

I guess I can sum it up with this failing test:

     before(:each) do
       @source_comment = @source.comments.create(:user_id => 1)
       @target_comment = @target.comments.create(:user_id => 1)
     end

     it "should return the same object from create() as from the

array" do

       (@target_comment.equal?(@target.comments[0])).should be_true

       do_merge

     end

What I actually want to be able to do, is mock a method call on the
@target_comment but I’m finding that I get unreliable results:

       @target.comments[0].should_receive(:merge_in) # works

@target_comment.should_receive(:merge_in) # doesn’t work

The code I’m testing is using self.comments.each() to access the
object it’s going to call merge_in() on - the one I want to mock.

Any tips here folks?

Someone here has suggested that what I really need to do is express
my mock like… (commence pseudo code)

Comment.should_receive_instance_method(:merge_in).with
(…).on_an_object_matching{|o| o.id = @target_comment.id}

(/pseudo code)

Has anyone else come across similar issues? How did you approach them?

cheers,
Matt

http://blog.mattwynne.net

In case you wondered: The opinions expressed in this email are my own
and do not necessarily reflect the views of any former, current or
future employers of mine.

On Tue, Aug 26, 2008 at 1:34 PM, Matt W. [email protected] wrote:

do
The code I’m testing is using self.comments.each() to access the object it’s
going to call merge_in() on - the one I want to mock.
Any tips here folks?
Someone here has suggested that what I really need to do is express my mock
like… (commence pseudo code)
Comment.should_receive_instance_method(:merge_in).with(…).on_an_object_matching{|o|
o.id = @target_comment.id}
(/pseudo code)

Has anyone else come across similar issues? How did you approach them?

Here’s the basic deal:

Model.find(1).equal?(Model.find(1))
=> false

AR does not cache objects, so when you ask it for what you think
might the same object twice, you get different ones.

mocha offers an ‘any_instance’ method, which gives you basically what
you are describing with should_receive_instance_method, but rspec does
not yet have a counterpart in its mocking library.

I can tell you how I handle this, but let me say that this is a less
than perfect way to handle a less than perfect situation so it’s
difficult for me to say “this is a recommended approach.” That said

@target_comment = stub_model(Target)
@target.stub!(:comments).and_return([@target_comment])

HTH,
David

On Tue, Aug 26, 2008 at 5:01 PM, David C.
[email protected]wrote:

@target_comment = stub_model(Target)
@target.stub!(:comments).and_return([@target_comment])

That’s what we do - what would the drawbacks be?

///ark

On Tue, Aug 26, 2008 at 8:51 PM, Mark W. [email protected] wrote:

On Tue, Aug 26, 2008 at 5:01 PM, David C. [email protected]
wrote:

@target_comment = stub_model(Target)
@target.stub!(:comments).and_return([@target_comment])

That’s what we do - what would the drawbacks be?

It’s more invasive than I’d like. It’s not all that risky though. I
just feel dirty whenever I mock methods on the objects I’m focused on.
FWIW.

Cheers,
David

On Tue, Aug 26, 2008 at 10:03 PM, David C. [email protected]
wrote:

It’s more invasive than I’d like. It’s not all that risky though. I
just feel dirty whenever I mock methods on the objects I’m focused on.
FWIW.

A similar approach I’ve used when I’m not utilizing custom SQL logic
(that requires hitting the database) which is less “dirty” IMO than
modifying the object you’re focusing on is:

comments = [stub_model(Target)]
@target.comments = comments


Zach D.
http://www.continuousthinking.com


Zach D.
http://www.continuousthinking.com

On Wed, Aug 27, 2008 at 3:34 AM, Matt W. [email protected] wrote:

Here’s the basic deal:

Model.find(1).equal?(Model.find(1))
=> false

AR does not cache objects, so when you ask it for what you think
might the same object twice, you get different ones.

I thought as much… So does AR just cache the object’s attributes instead
and construct them on the fly as and when you ask for them?

It caches the SQL statements and their results. It uses the cached
results to build an instance of your model. Although the identify of
the objects are different, they are equal.

f = Foo.create :name => "blah"
f.equal?(Foo.last) # false
f == Foo.last       # true

I don’t know your ultimate goal, but you could rely on object equality
(and not identify equality) in your test:

@target_comment.should == @target_comments.first

And then rely on @target_comments.first to set up your expectation for
:merge_in.


Zach D.
http://www.continuousthinking.com

On Wed, Aug 27, 2008 at 7:22 AM, Zach D. [email protected]
wrote:

and construct them on the fly as and when you ask for them?
I don’t know your ultimate goal,
He’s trying to set a message expectation on an object that gets
returned by AR. Since AR builds a new object for each request, you
can’t get a handle on it in the code example.

Here’s the basic deal:

Model.find(1).equal?(Model.find(1))
=> false

AR does not cache objects, so when you ask it for what you think
might the same object twice, you get different ones.

I thought as much… So does AR just cache the object’s attributes
instead and construct them on the fly as and when you ask for them?

mocha offers an ‘any_instance’ method, which gives you basically what
you are describing with should_receive_instance_method, but rspec does
not yet have a counterpart in its mocking library.

Interesting. I’d love to dive in and write one for RSpec but I think
it might be a bit beyond me right now. You can’t really mix mocking
frameworks in RSpec, right?

I can tell you how I handle this, but let me say that this is a less
than perfect way to handle a less than perfect situation so it’s
difficult for me to say “this is a recommended approach.” That said

@target_comment = stub_model(Target)
@target.stub!(:comments).and_return([@target_comment])

Bugger - I was hoping you wouldn’t say that :slight_smile:

Okay well at least I know what the deal is. Thanks as usual David.

cheers,
Matt

On 27 Aug 2008, at 13:26, David C. wrote:

might the same object twice, you get different ones.
f = Foo.create :name => “blah”
f.equal?(Foo.last) # false
f == Foo.last # true

I don’t know your ultimate goal,

He’s trying to set a message expectation on an object that gets
returned by AR. Since AR builds a new object for each request, you
can’t get a handle on it in the code example.

You’ve got it. I worked around it using the foo.stub!(:bars) = []
hack, but I too feel dirty about it.

I think I’m going to need a couple of old-fashioned database-coupled
tests to prove to myself that all this works OK.

On Wed, Aug 27, 2008 at 2:34 AM, Matt W. [email protected] wrote:

mocha offers an ‘any_instance’ method, which gives you basically what
you are describing with should_receive_instance_method, but rspec does
not yet have a counterpart in its mocking library.

Interesting. I’d love to dive in and write one for RSpec but I think it
might be a bit beyond me right now.

http://rubyurl.com/PHwC - it got started but a) the patch was never
completed and b) there are some mixed feelings about supporting it.
Feel free to add to the conversation in that ticket.

You can’t really mix mocking frameworks
in RSpec, right?

Right.