Hoptoad—which 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.
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:
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
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:
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:
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 proxy_object
module variable.
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
):
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.