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 http://songkick.com 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. -------------- next part -------------- An HTML attachment was scrubbed... URL: <http://rubyforge.org/pipermail/rspec-users/attachments/20080826/ee5a542d/attachment.html>
David Chelimsky
2008-Aug-27 00:01 UTC
[rspec-users] stub_model() and ActiveRecord Associations
On Tue, Aug 26, 2008 at 1:34 PM, Matt Wynne <matt at mattwynne.net> wrote:> 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?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> cheers, > Matt
Mark Wilden
2008-Aug-27 01:51 UTC
[rspec-users] stub_model() and ActiveRecord Associations
On Tue, Aug 26, 2008 at 5:01 PM, David Chelimsky <dchelimsky at gmail.com>wrote:> @target_comment = stub_model(Target) > @target.stub!(:comments).and_return([@target_comment]) >That''s what we do - what would the drawbacks be? ///ark -------------- next part -------------- An HTML attachment was scrubbed... URL: <http://rubyforge.org/pipermail/rspec-users/attachments/20080826/40801148/attachment.html>
David Chelimsky
2008-Aug-27 02:03 UTC
[rspec-users] stub_model() and ActiveRecord Associations
On Tue, Aug 26, 2008 at 8:51 PM, Mark Wilden <mark at mwilden.com> wrote:> On Tue, Aug 26, 2008 at 5:01 PM, David Chelimsky <dchelimsky at gmail.com> > 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> > ///ark > > _______________________________________________ > rspec-users mailing list > rspec-users at rubyforge.org > http://rubyforge.org/mailman/listinfo/rspec-users >
Zach Dennis
2008-Aug-27 02:26 UTC
[rspec-users] stub_model() and ActiveRecord Associations
On Tue, Aug 26, 2008 at 10:03 PM, David Chelimsky <dchelimsky at gmail.com> wrote:> On Tue, Aug 26, 2008 at 8:51 PM, Mark Wilden <mark at mwilden.com> wrote: >> On Tue, Aug 26, 2008 at 5:01 PM, David Chelimsky <dchelimsky at gmail.com> >> 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.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 Dennis http://www.continuousthinking.com http://www.mutuallyhuman.com -- Zach Dennis http://www.continuousthinking.com http://www.mutuallyhuman.com
> 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 :) Okay well at least I know what the deal is. Thanks as usual David. cheers, Matt
Zach Dennis
2008-Aug-27 12:22 UTC
[rspec-users] stub_model() and ActiveRecord Associations
On Wed, Aug 27, 2008 at 3:34 AM, Matt Wynne <matt at mattwynne.net> 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 Dennis http://www.continuousthinking.com http://www.mutuallyhuman.com
David Chelimsky
2008-Aug-27 12:26 UTC
[rspec-users] stub_model() and ActiveRecord Associations
On Wed, Aug 27, 2008 at 7:22 AM, Zach Dennis <zach.dennis at gmail.com> wrote:> On Wed, Aug 27, 2008 at 3:34 AM, Matt Wynne <matt at mattwynne.net> 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,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.> 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 Dennis > http://www.continuousthinking.com > http://www.mutuallyhuman.com > _______________________________________________ > rspec-users mailing list > rspec-users at rubyforge.org > http://rubyforge.org/mailman/listinfo/rspec-users >
David Chelimsky
2008-Aug-27 12:32 UTC
[rspec-users] stub_model() and ActiveRecord Associations
On Wed, Aug 27, 2008 at 2:34 AM, Matt Wynne <matt at mattwynne.net> 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? > >> 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.>> 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 :) > > Okay well at least I know what the deal is. Thanks as usual David. > > cheers, > Matt > _______________________________________________ > rspec-users mailing list > rspec-users at rubyforge.org > http://rubyforge.org/mailman/listinfo/rspec-users >
On 27 Aug 2008, at 13:26, David Chelimsky wrote:> On Wed, Aug 27, 2008 at 7:22 AM, Zach Dennis > <zach.dennis at gmail.com> wrote: >> On Wed, Aug 27, 2008 at 3:34 AM, Matt Wynne <matt at mattwynne.net> >> 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, > > 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.