Testing HTTP Errors With Ruby

Dan Croak

A common pattern in our apps is handling failure by notifying Airbrake. A good example is hitting a third party web service such as Twitter.

def fetch_tweets(search)
  search.fetch
rescue *(HTTP_ERRORS + Twitter::Search::ERRORS) => error
  HoptoadNotifier.notify(error)
  []
end

We send rescued errors to Airbrake, shielding our users from 500s with relevant copy.

Thunder Thimble Errno errors in Hoptoad

This uses the Twitter gem. It separates building a query (object initialization) and making the HTTP request (fetch method).

Isolating HTTP calls

Somewhere else, we do heavy-duty query building. We then isolate the HTTP request inside it’s own method and rescue an expected class of errors.

Aside from testing failure, this isolation makes our other tests easier. We can stub the fetch_tweets method and forget about the interface to the Twitter library.

Arrays of error types

HTTP_ERRORS comes from the Suspenders’ errors initializer and Twitter::Search::ERRORS is something custom.

Stubs and spies

We use Bourne for its test spies.

Testing failure

*(HTTP_ERRORS + Twitter::Search::ERRORS).each do |error|
  should "notify hoptoad upon #{error} on fetch tweets" do
    brand = build(:brand)
    search = stub('search')
    search.stubs(:fetch).raises(error.new(''))
    Airbrake.stubs(:notify)

    brand.fetch_tweets(search)

    assert_received(Airbrake, :notify) do |expects|
      expects.with(is_a(error))
    end
  end
end

This is a classic stub-and-spy test. It uses the normal Four-Phase Test structure. The extra newlines separate the setup, exercise, and verification phases. The test is flat; it does not use a context block.