Search code examples
rubyerror-handlingtddmechanize-ruby

What is proper way to test error handling?


I've been working with the Mechanize gem a lot recently and would like to incorporate some tests to ensure I am catching the proper errors. What is the proper way to test error handling?

This is my basic method:

def get(str)
  url = format_url(str)
  #puts "sending GET request to: #{url}"
  sleep(0.1)
  @page = Mechanize.new do |a|
    a.user_agent_alias = 'Mac Safari'
    a.open_timeout = 7
    a.read_timeout = 7
    a.idle_timeout = 7
    a.redirect_ok = true
  end.get(url)

rescue Mechanize::ResponseCodeError => e
  puts "#{'Response Error:'.red} #{e}"
rescue SocketError => e
  puts "#{'Socket Error:'.red} #{e}"
rescue Net::OpenTimeout => e
  puts "#{'Connection Timeout:'.red} #{e}"
rescue Errno::ETIMEDOUT => e
  puts "#{'Connection Timeout:'.red} #{e}"
rescue Net::HTTP::Persistent::Error
  puts "#{'Connection Timeout:'.red} read timeout, too many resets."
end

And this is the start to tests for handling errors:

class TestErrorHandling < Mechanize::TestCase
  context 'Example when sending a GET request' do
    should 'rescue error and return nil' do
      assert_equal nil, Example.get('http://localhost/pagethatdoesntexist')
    end
  end
end

Am I heading in the correct direction? Any insight and/or resources welcome.


Solution

  • Sort of. You shouldn't test dependent libraries again in your application. It's enough to catch the Net::HTTP::Persistent::Error without ensuring the underlying functionality is working. Well written gems should provide their own tests, and you should be able to access those tests as needed by testing that gem (Mechanize, for example).

    You could mock for those errors, but you should be judicious. Here is some code to mock an SMTP connection

     class Mock
        require 'net/smtp'
    
        def initialize( options )
          @options = options
          @username = options[:username]
          @password = options[:password]
          options[:port] ? @port = options[:port] : @port = 25
          @helo_domain = options[:helo_domain]
          @from_addr = options[:from_address]
          @from_domain = options[:from_domain]
    
          #Mock object for SMTP connections
          mock_config = {}
          mock_config[:address] = options[:server]
          mock_config[:port] = @port
    
          @connection = RSpec::instance_double(Net::SMTP, mock_config)
    
          allow(@connection).to receive(:start).and_yield(@connection)
          allow(@connection).to receive(:send_message).and_return(true)
          allow(@connection).to receive(:started?).and_return(true)
          allow(@connection).to receive(:finish).and_return(true)
        end
        #more stuff here
     end
    

    I don't see you testing for any custom errors which would make more sense here. For example, you might test for url-unfriendly characters in your parameter and rescue from that. In that case, your test would offer something explicit.

     expect(get("???.net")).to raise_error(CustomError)