Search code examples
rubyrspecraiseopen-uri

RSpec and Open-URI how do I mock raise a SocketError/TimeoutError


I want to be able to spec out that when Open-Uri open() calls either timeout or raise an exception such as SocketError I am handling things as expected, however I'm having trouble with this.

Here is my spec (for SocketError):

@obj.should_receive(:open).with("some_url").and_raise(SocketError)

And the part of my object where I'm using open-uri:

begin
  resp = open(url)
  resp = resp.read
rescue SocketError
  something = true
end

However in this situation the spec fails as with a nil.read error.

This is the second time this week I've come across this problem, the previous time I was attempting to simulate a TimeoutError when wrapping open() with a timeout() {}, that time I gave up and just caused an actual timeout to happen by opening up the class. I could obviously cause this to throw a SocketError by trying to call an invalid URL, but I'm sure there is a correct way to mock this out with RSpec.

Update: I obviously wasn't thinking clearly that late at night, the error was actually when I re-tried the URL after the SocketError, the and_raise(SocketError) part worked fine.


Solution

  • The line you provided should work, based on the information you've given: I made a tiny test class and spec (see below) with only the described functionality, and things behaved as expected. It might be helpful if you could provide a little more context - the full "it" block from the spec, for instance, might expose some other problem.

    As mentioned, the following spec passes, and I believe it captures the logic you were attempting to verify:

    require 'rubygems'
    require 'spec'
    
    class Foo
      attr_accessor :socket_error
    
      def get(url)
        @socket_error = false
        begin
          resp = open(url)
          resp = resp.read
        rescue SocketError
          @socket_error = true
        end
      end
    end
    
    describe Foo do
      before do
        @foo = Foo.new
      end
    
      it "should handle socket errors" do
        @foo.should_receive(:open).with("http://www.google.com").and_raise(SocketError)
        @foo.get("http://www.google.com")
        @foo.socket_error.should be_true
      end
    end