tags:

views:

3052

answers:

7
class A
  def initialize
    @x = do_something
  end

  def do_something
    42
  end
end

How can I stub do_something in rspec, before the original implementation is called (thus assigning 42 to @x)? And without changing the implementation, of course.

+3  A: 

I don't know how to do that in spec's mock framework, but you can easily swap it out for mocha to do the following:

# should probably be in spec/spec_helper.rb
Spec::Runner.configure do |config|
  config.mock_with :mocha
end

describe A, " when initialized" do
  it "should set x to 42" do
    A.new.x.should == 42
  end
end

describe A, " when do_something is mocked" do
  it "should set x to 23" do
    A.any_instance.expects(:do_something).returns(23)
    A.new.x.should == 23
  end
end
Dustin
+3  A: 

or with RR:

stub.any_instance_of(A).do_something { 23 }
Chris Lloyd
+3  A: 

Here's the commit which adds the feature to rspec - This was on May 25 2008

However, the latest gem version of rspec (1.1.11, October 2008) doesn't have this patch in it.

This ticket states that they yanked it out for maintenance reasons, and an alternative solution hasn't yet been provided.

Doesn't look like you can do it at this point. You'll have to hack the class manually using alias_method or somesuch

Orion Edwards
+1  A: 

In my version of rspec (1.2.2) I can do this:

A.should_receive(:new).and_return(42)

I know it is probably to late to answer to the original poster, but I answer it anyway for future reference, as I came here with the same question but figure it out it was working on the latest rspec version.

pschneider
The behaviour of your suggestion is not correct. It's the :do_something method which returns 42, not the initializer.
Andrew Vit
A: 

To stub an instance method, you can do something like this:

before :each do
  @my_stub = stub("A")
  @my_stub.should_receive(:do_something).with(no_args()).and_return(42)
  @my_stub.should_receive(:do_something_else).with(any_args()).and_return(true)
  A.stub(:new).and_return(my_stub)
end

But as pschneider pointed out, just return 42 on new with: A.stub(:new).and_return(42) or something to that effect.

bluehavana
I think the point is to stub the instance method when you're not in control of creating the instances in your test. Plus, the initializer doesn't return 42, it sets an instance variable: it's the do_something method that returns 42 which needs to be stubbed.
Andrew Vit
The `A.stub(:new).and_return(@my_stub)` will return the instance stub every time, thus giving you control over the instance creation.As far as setting the member variable, I've always heard you're not supposed to worry about internals in mocking/stubbing, just public interfaces. So, just return the correct values for the method that depends upon `@x`.I'm probably wrong though
bluehavana
+4  A: 

I've found this solution on http://pivots.pivotallabs.com/users/brian/blog/articles/352-introducing-rr

new_method = A.method(:new)

A.stub!(:new).and_return do |*args|
  a = new_method.call(*args)
  a.should_receive(:do_something).and_return(23)
  a
end
Denis Barushev
+1  A: 

I do like Denis Barushev answer. And I'd like to suggest only one cosmetic change which makes new_method variable unnecessary. Rspec does munge on stubbed methods, so that they can be accessed with proxied_by_rspec__ prefix:


A.stub!(:new).and_return do |*args|
  a = A.proxied_by_rspec__new(*args)
  a.should_receive(:do_something).and_return(23)
  a
end
Serge Balyuk
This is a really nice idea, but it fails to work with the version of RSpec I'm using. Denis' answer does work.
emk
Thanks! Yes, it's very dependent on "proxied_by_rspec__" name prefix convention which may not be a part of published API. Oh, and ".call" is not needed on the second line, sorry. BTW what RSpec version are you using?
Serge Balyuk