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)
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.
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
…
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:
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.
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.
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
…
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.
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.
This forum is not affiliated to the Ruby language, Ruby on Rails framework, nor any Ruby applications discussed here.