CurbFu - Now With Killer Testing

Matt Wilson and I have been making some awesome new updates to CurbFu - our convenient wrapper for Ruby’s curb gem - itself an wrapper around libcurl.

Our latest improvement revolves around integration testing. At Greenview Data, we have several Rack apps that talk to each other, including a Rails app. While all of code for the individual apps was unit tested, we still lacked a good integration test to make sure everything played well together.

In the past I had tried to set up a VM instance that was controlled by a series of RSpec Story Runner tests. Getting the test server instances updated and running was a pain, managing all of the running processes was a pain (especially testing a system with down processes), and writing tests to verify data became quite contrived.

With the introduction of Bryan Helmkamp’s Rack::Test and Rails 2.3’s support for Rack, I dreamed of having these apps running in memory, able to send HTTP requests to each other without having to set up a separate integration testing system. Now that all of our apps use CurbFu to send HTTP requests to each other, we seized the opportunity to create a testing module that override’s CurbFu’s get/post/put/delete operations. Instead of sending a real HTTP request over the internet, CurbFu will now call any rack apps you specify directly using Rack::Test.

Essentially, this system is similar to FakeWeb and Webrat’s newly added Rack support, but the difference is that if you use CurbFu for all of your HTTP communication, you can quickly and easily set up an integration test environment or set up smart mocks of HTTP services.

How does it work?

require ‘curb-fu’
CurbFu.stubs = {
‘a.example.com’ => RackApp1.new,
‘b.example.com’ => RackApp2.new
}
class RackApp1
def call(env)
CurbFu.get(‘http://b.example.com’)
end
end
class RackApp2
def call(env)
puts “WOW!”
end
end
CurbFu.get(‘a.example.com’)
#=> WOW!

require 'curb-fu'

CurbFu.stubs = {
  'a.example.com' => RackApp1.new,
  'b.example.com' => RackApp2.new
}

class RackApp1
  def call(env)
    response = Rack::Response.new
    response.status, response.body = [200, '']
    CurbFu.get('http://b.example.com')
    response.finish
  end
end

class RackApp2
  def call(env)
    response = Rack::Response.new
    response.status, response.body = [200, "WOW!"]
    response.finish
  end
end

puts CurbFu.get('a.example.com').body
#=> WOW!

</code>

Loading a Rails app is a bit more involved:

require 'curb-fu'
require '/path/to/rails/app/config/environment'
require 'dispatch'

CurbFu.stubs = {
  'my.cool.railsa.pp' => ActionController::Dispatcher.new
}

</code>

We’ve set up a series of Cucumber stories to test the interplay between our various rack apps. By having the rails app in memory, we’re able to access ActiveRecord objects from our step definitions, as if we’re within the rails app. We’re also able to stub specific methods on objects within our rack apps and we throw up quick little Rack apps that can pretend to be things like a Solr server or a site hosting an RSS feed. The sky is the limit!

Once caveat we’ve run into is the issue of namespacing. It may be a good idea to namespace your Rails model classes as they may conflict with other loaded Rack apps.

Have fun testing!

Links: CurbFu