Topic: Test doubles for inaccessible variables

I've finally bit the bullet and I'm using rspec for all my ruby development. However, I'm having trouble with designing objects to support testing. Particularly, how do I provide a test double for something that is instantiated internally in an object? In the rspec book they always just create the double and then pass it in to the object. However, passing the object in usually only makes sense in a testing situation. For example,

class SomeExternalAPI
  def initialize
    @internal_obj = OAuth::Consumer.new(stuff)
  end

  def do_something
    results = @internal_obj.request(:post, "site")
    return munge_results(results)
  end
end

Now I want to stub out the request so that it returns a canned response. However, I don't have access to @internal_obj, and requiring an OAuth::Consumer to be passed in does not make sense. Unless I make an inner class that requires the OAuth::Consumer, and then I test that object instead.

The only other solution I can think of is to write a method in SomeExternalAPI that initializes the oauth object. Then I can stub that method out and return a test double?

So what is best practice for cutting off an internal object like this?

Thanks for any thoughts

Re: Test doubles for inaccessible variables

I might be misunderstanding the problem, but I think you should be able to stub OAuth::Consumer.new. This code works (I wrote a quick fake OAuth::Consumer class just for demonstration).

module OAuth
  class Consumer
  end
end

class SomeExternalApi
  def initialize(stuff)
    @internal_obj = OAuth::Consumer.new(stuff)
  end

  def do_something
    results = @internal_obj.request(:post, "site")
    return munge_results(results)
  end
end

require File.dirname(__FILE__) + '/../spec_helper'
require 'some_external_api'

describe SomeExternalApi do
  it "does stuff" do
    mock_oauth_response = {:something => 'here'} 
    oauth_mock = mock('Oauth')
    oauth_mock.stubs(:request).returns(mock_oauth_response)
    OAuth::Consumer.expects(:new).with('stuff').returns(oauth_mock)
    sea = SomeExternalApi.new('stuff')
    sea.expects(:munge_results).with(mock_oauth_response).returns('munged_stuff')
    sea.do_something.should == 'munged_stuff'
  end
end

It might seem like it's over-stubbing/mocking, but it is focusing on testing the interface.  It assumes you have full test coverage on the method 'munge_results' as well.  I'd also highly recommend having one test that actually makes an OAuth connection.  This is handled well by Cucumber.

-Bryan

Re: Test doubles for inaccessible variables

p.s., I used mocha syntax for that example.  Change the "expects" to "should_receive", etc. for built-in RSpec doubles.

Re: Test doubles for inaccessible variables

Thanks bdondo. You're absolutely correct. I just needed to stub out :new on the class. My thinking was still being held back more old school unit testing approaches in languages that aren't as awesome as ruby.

Re: Test doubles for inaccessible variables

No problem, glad that works.