should_receive not failing but not actually used?

80 views
Skip to first unread message

Christophe Porteneuve

unread,
Apr 27, 2012, 4:54:09 AM4/27/12
to rspec
Hi there,

Google didn't yield anything useful, so here I am. This is on a
legacy project: Rails 2.3.14, rspec 1.3.2, rspec-rails 1.3.4 :-/

I've got a before(:each) setting up AR instances, so that I end up
with a @product assign.

Then within a context, one of my "it" descriptions goes:


money = Money.new(5_00, 'EUR')
fd = double('FlashSaleDiscount', :sale_total_cents =>
money.cents)
Rails.logger.debug '1>' * 70

@product.should_receive(:active_flash_sale_discount).and_return(fd)
@product.min_money(:force).should == money
Rails.logger.debug '<1' * 70

But when I run this, @product's *actual* active_flash_sale_discount
method is called, not the stubbed version used by the should_receive
expectation.

Another thing that confuses me is that the before(:each) AR-creating
code is, according to logs, triggered *after* the first
Rails.logger.debug call. Is there some lazy evaluation stuff I'm not
aware of in there? As for the final call, it doesn't show up in my
logs since the should fails.

This test code used to work. I have no idea why it suddenly went dead.
I thought this might be due to my running it through spork and guard,
but reverting back to a bare-bones "spec spec/models/product_spec.rb"
CLI had the exact same results.

Any help appreciated!

Thanks,

David Chelimsky

unread,
Apr 27, 2012, 8:47:15 AM4/27/12
to rs...@googlegroups.com
On Friday, April 27, 2012 at 3:54 AM, Christophe Porteneuve wrote:
This is on a legacy project: Rails 2.3.14, rspec 1.3.2, rspec-rails 1.3.4 :-/

I've got a before(:each) setting up AR instances, so that I end up
with a @product assign.

Then within a context, one of my "it" descriptions goes:

money = Money.new(5_00, 'EUR')
fd = double('FlashSaleDiscount', :sale_total_cents =>
money.cents)
Rails.logger.debug '1>' * 70
@product.should_receive(:active_flash_sale_discount).and_return(fd)
@product.min_money(:force).should == money
Rails.logger.debug '<1' * 70

But when I run this, @product's *actual* active_flash_sale_discount
method is called, not the stubbed version used by the should_receive
expectation.
There is a guideline that you should not stub or set message expectations on the object you're testing. Doing so changes the runtime structure of the object in a way that never happens in the running system so it's quite possible to end up with false results. Not saying this is the source of your issue. Just saying :)

Usually when you set an expectation on an ActiveRecord model and the real message gets called it's because there is a finder involved that is building a new instance of the same data e.g.

  describe Thing do
    before do
      @thing_helper = ThingHelper.create!
      @thing = Thing.create!(:thing_helper_id => @thing_helper.id)
    end

    it "delegates to the helper" do
      @helper.should_receive(:help_me)
      @thing.do_something_that_needs_help
    end
  end

  class Thing < ActiveRecord::Base
    belongs_to :thing_helper
    def do_something_that_needs_help
      thing_helper.help_me
    end
  end

Here the do_something_that_needs_help loads a new copy of the ThingHelper with the same data as the @thing_helper set up in the before block, and sends `help_me` to that object instead.

I'd need to see the code the in the before(:each) block to be sure, but that is one place to look.
Another thing that confuses me is that the before(:each) AR-creating
code is, according to logs, triggered *after* the first
Rails.logger.debug call. Is there some lazy evaluation stuff I'm not
aware of in there? As for the final call, it doesn't show up in my
logs since the should fails.

This test code used to work. I have no idea why it suddenly went dead.
I thought this might be due to my running it through spork and guard,
but reverting back to a bare-bones "spec spec/models/product_spec.rb"
CLI had the exact same results.
Did anything change since the code did work? Diff version of Ruby? Upgraded any gems? Etc, etc.

David

Christophe Porteneuve

unread,
Apr 27, 2012, 9:13:59 AM4/27/12
to rs...@googlegroups.com
Hey David, thanks for your time and reply (the creator of RSpec,
whooohooo! :)).

I get your point about expectations on objects undergoing test, but then
I wonder what's the best way to go about it when you want to test a
specific method in isolation of its same-object-method dependencies? I
don't recall having seen such warnings in the RSpec book (but, granted,
I read that quite a while ago).

Also, I don't believe the "hidden re-instanciation" is the issue at play
here, as I'm not doing anything between @product.should_receive and
@product.min_money(�).should.

However, the logs tell me the AR instanciation actually happens AFTER my
'1>' marker, so perhaps you're right, but then why can't I rely on the
before block being actually run, AR-wise, BEFORE my description?!

As for Ruby and gems, nope, last clear run was on the same basis.
However, the code being exercised did change, but not the specific part
I'm grappling with here (expectations on methods then being called
internally) by the tested method.

Thanks again for your time, I appreciate it.

--
Christophe Porteneuve
Directeur technique, Ciblo.net
chris...@ciblo.net

Christophe Porteneuve

unread,
Apr 27, 2012, 9:19:44 AM4/27/12
to rs...@googlegroups.com
Hey David,

Just so you can better visualize the interplay here, I put selected
segments of my model and spec in a gist:

https://gist.github.com/b1eac83fecf3219dd534

You can see how active_flash_sale_discount is called immediately within
min_money(:force), and how my expectation happens just before I call
min_money. So I fail to see how it could have been swapped with another
instance not bearing the expectation.

In the end the original active_flash_sale_discount method is called,
returning no discount, and my test tanks.

I think I'll just fix this by actually injecting the proper models in
there and not rely on expectations instead (which won't let me confirm
the method WAS called, but getting the proper final result will be
enough). Still, I'd love to understand what gives here.

Best,

dchel...@gmail.com

unread,
Apr 27, 2012, 9:57:58 AM4/27/12
to rs...@googlegroups.com
On Friday, April 27, 2012 8:13:59 AM UTC-5, Christophe Porteneuve wrote:
Hey David, thanks for your time and reply (the creator of RSpec,
whooohooo! :)).

Steven Baker is the creator of RSpec, but thanks for the enthusiasm.

--
David

Christophe Porteneuve

unread,
Apr 27, 2012, 10:10:26 AM4/27/12
to rs...@googlegroups.com
Dang! That's what numerous books and conference talks give you. My
apologies to Steven :-)

dchel...@gmail.com

unread,
Apr 27, 2012, 10:17:00 AM4/27/12
to rs...@googlegroups.com
On Friday, April 27, 2012 8:13:59 AM UTC-5, Christophe Porteneuve wrote:
I get your point about expectations on objects undergoing test, but then
I wonder what's the best way to go about it when you want to test a
specific method in isolation of its same-object-method dependencies?

Stub the collaborators, but not the methods on the object under test.
 
 I
don't recall having seen such warnings in the RSpec book (but, granted,
I read that quite a while ago).

They're there.
 
Also, I don't believe the "hidden re-instanciation" is the issue at play
here, as I'm not doing anything between @product.should_receive and
@product.min_money(�).should.

However, the logs tell me the AR instanciation actually happens AFTER my
'1>' marker, so perhaps you're right, but then why can't I rely on the
before block being actually run, AR-wise, BEFORE my description?!

before blocks are absolutely 100% without a doubt run before the example. I think Rails logging is asynchronous, but having trouble locating relevant docs. Try puts statements instead.
Reply all
Reply to author
Forward
0 new messages