JohnDel
JohnDel

Reputation: 2112

Rails: How to stub and test this external service call?

Hello I have something like the following code inside a rails model:

class Page < ActiveRecord::Base
  def calculate_page_speed(url)
    browser = Watir::Browser.new :phantomjs
    start = Time.now
    browser.goto url
    finish = Time.now
    self.speed = finish - start
  end
end

Here is the test:

describe Page do
  context "calculate_page_speed for Page" do
    let(:google) { FactoryGirl.create(:page, url: "http://www.google.com") }

    it "should set the page speed" do
      google.speed.should be_nil
      google.calculate_page_speed
      google.speed.should_not be_nil
    end
  end
end

How can I stub effectively the external service for not calling it during the test?

Upvotes: 1

Views: 699

Answers (2)

apneadiving
apneadiving

Reputation: 115541

There is an approach like:

class Page < ActiveRecord::Base
  def calculate_page_speed(url)
    start = Time.now
    browser.goto url
    finish = Time.now
    self.speed = finish - start
  end

  def browser
    @browser ||= Rails.env.test? ? FakeBrowser.new : Watir.new(:phantomjs)
  end

  class FakeBrowser
    def goto
    end
  end 
end

but here you have test code in prod code which is not great.


A derived solution would be to inject the expected browser class in a config.


Finally you can just stub stuff:

Watir::Browser.any_instance.stub(:goto)

Upvotes: 2

nathanvda
nathanvda

Reputation: 50057

I do not think that visiting a url and measuring the speed belongs inside the Page model, setting the speed is, but not the actual calculation. So I would create a class in lib that allows me to measure the speed of a url, and use that class instead. I would allow that class to be injected with a stub.

So something like this, in lib\page_visitor.rb:

class PageVisitor 

  def initialize(browser = Watir::Browser.new(:phantomjs)) 
    @browser = browser
  end

  def measure_speed(url) 
    start = Time.now
    @browser.goto url
    finish = Time.now
    finish - start  
  end

end 

And the your Page becomes:

def calculate_page_speed(url) 
  self.speed = PageVisitor.new.measure_speed(url)
end

So with that setup, you can simplify your tests: in Page you have to check the right function is called, and likewise, in PageVisitor check that goto is called, without actually going to the page.

So, in spec/models/page_spec.rb write:

 let(:google) { FactoryGirl.create(:page, url: "http://www.google.com") }

 it("initial speed is zero") { google.speed.should be_nil }

 describe `calculating the speed` do 
   before do
     PageVisitor.any_instance.should_receive(:measure_speed).and_return(25)
     google.calculate_page_speed
   end 

   it "sets the speed correctly"        
     google.speed.should == 25
   end
 end 

Using any_instance could be considered a smell, but in this case I think it is the easiest way to test it. If you really want to avoid it, instead of changing the code (I think the code is ok, no need to inject a PageVisitor imho), you could stub PageVisitor.new to return a specific PageVisitor, and then you can just stub measure_speed on that instance.

In your spec/lib/page_visitor_spec.rb write

 describe PageVisitor 

   class FakeBrowser 
     def goto(url)
       sleep 1
     end
   end

   let(:page_visitor) { PageVisitor.new(FakeBrowser.new) } 

   it "measure the speed" do 
     page_visitor.measure_speed.should_not be_nil
   end
 end

This should get you started.

Upvotes: 1

Related Questions