A HTTP testing proxy

Posted by Mike Burns

Jul 25

Hoptoadwhich is now live—is both an application and a Rails plugin that must work together. This integration simply cannot go untested in a test-happy place like thoughtbot.

Battletoads

The plugin, I'm sure you've seen, has a private method #send_to_hoptoad that handles the dirty HTTP stuff. It looks like a more complicated version of this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
def send_to_hoptoad(data)
  url = HoptoadNotifier.url
  Net::HTTP.start(url.host, url.port) do |http|
    headers = {
      'Content-type' => 'application/x-yaml',
      'Accept' => 'text/xml, application/xml'
    }
    response = begin
                 http.post(url.path, stringify_keys(data).to_yaml, headers)
              rescue TimeoutError => e
                 nil
               end
    case response
    when Net::HTTPSuccess then
      logger.info "Hoptoad Success"
    else
      logger.error "Hoptoad Failure"
    end
  end
end

Dead frog controlled via a network

The integration test simulates the plugin actually hitting the application. Normally to test #send_to_hoptoad you'd use Mocha to stub out Net::HTTP methods, but stubbing sweeps away too many potential issues here.

What we really want is an integration test that pits the plugin against the real application, without running a server. We want Net::HTTP#post to use ActionController::Integration::Session#post .

The gruesome internals

In the integration test for the application, first require in the needed tricks:

1
2
3
require 'test_helper'
require 'net/http'
require File.dirname(__FILE__) + '/../lib/hoptoad_notifier/lib/hoptoad_notifier'

(we've installed a copy of the plugin into test/lib)

Then, open up Net::HTTP and get rid of the bits that connect to the network. This part could be done with Mocha, but we need to open Net::HTTP later so we might as well do it this way:

1
2
3
4
class Net::HTTP < Net::Protocol
  def connect
  end
end

While you have Net::HTTP open, replace #post with a proxy. The class to proxy to is passed into the test_unit_class module variable.

1
2
3
4
5
6
7
class Net::HTTP < Net::Protocol
  mattr_accessor :proxy_object

  def post(path, body, headers)
    self.class.proxy_object.post path, body, headers
  end
end

Finally in the test setup block we need to initialize Net::HTTP with the appropriate instance of ActionController::Integration::Session (which is to say, self):

1
2
3
4
5
6
7
8
9
10
class PostingFromHoptoadNotifierTest < ActionController::IntegrationTest
  context "with a connection from the plugin to the application" do
    setup do
      Net::HTTP::proxy_object = self
    end

    should_eventually "deny access to people who disagree with me" do
    end
  end
end

All #should statements inside the context will proxy themselves through the integration test instead of hitting the network. Bam!

Check out the complete test file.

Look ma, no OSI layer 7!


Comments on this post

atmos

Jul 25

atmos said,

I can’t believe that dude’s hostname is stripper. You guys set the bar so high!

With mocking/stubbing getting so much press it’s nice to see people guiding folks in the right direction. Well done as always.

Aaron Gibralter

Jul 26

Aaron Gibralter said,

Is the part that sends data to Hoptoad blocking? I guess with a 2 or 5 second timeout it’s not so bad… But what about offering the ability to offload the sending operation to workling or backgroundrb, etc.?

Matt Jankowski

Jul 26

Matt Jankowski said,

Aaron – yes, that’s one of the last remaining issues from the EN that we plan on correcting. As it stands now there is a shortish timeout, so it won’t block forever…but any waiting at all is not really acceptable.

Our plan is to offload the reporting in a queue based system in the plugin itself, so that control can return to the framework immediately after the error is caught, and errors can be reported up to the web service in the background.

This is not in place yet, but it’s high on the list for next iteration.

Ken Collins

Jul 27

Ken Collins said,

The hoptoad account signup seems to not be working. Keeps not allowing newly created accounts to login.

Great article too! Coincidently, I just mentioned the Battle Toads game to my wife yesterday after we hooked up the 8-bit Nintendo to the 47” TV :)

Aaron Gibralter

Jul 29

Aaron Gibralter said,

I’m just curious why you ignore ActionController::RoutingError. I thought one of the reasons you created Hoptoad was that EN sent you too many emails for simple errors. Now that you have Hoptoad, shouldn’t you not ignore the simple, non-critical errors?

Jon Yurek

Jul 30

Jon Yurek said,

@Aaron: Well, yes and no. You’re right in that they’re simple errors, but we don’t generally care about them since they’re basically just 404s (and are almost all generated by spiders and spambots), plus they have the odd problem of defeating the duplicate detection we have (which is keyed to, among other things, action and controller), so we still get a lot of emails because they all appear to be different errors.


Sorry, comments are closed for this article.

© 2000 - 2009 by thoughtbot, inc.
written by a bushel of tiny robots