Jul 10 2008

Testing web services with ActiveResource

ActiveResource can be a useful tool for abstracting away low level HTTP or data marshaling details when testing web services with an XML schema and URI patterns which respect the Rails protocol for REST.

Here's a possible implementation for use in tests that exercise a service from the outside, a sort of black box web service testing approach, if you'd like.

def resource(name)
  class_name = name.to_s.camelize
  return class_name.constantize if Object.const_defined?(class_name.intern)
  rsrc = Class.new(ActiveResource::Base) do
    self.site = "http://localhost:4001/api"
    self.element_name = name.to_s
  end
  Object.const_set(class_name.intern, rsrc)
end

Let's imagine an API call to http://localhost:4001/api/categories.xml which returns a list of product categories with their respective subcategories. Following is a potential response to a GET request to the afore mentioned URI.

<?xml version="1.0" encoding="UTF-8"?>
<categories type="array">
  <category>
    <id type="integer">3</id>
    <name>Music</name>
    <subcategories type="array">
      <subcategory type="Category">
        <id type="integer">4</id>
        <name>Rock</name>
      </subcategory>
      <subcategory type="Category">
        <id type="integer">5</id>
        <name>Metal</name>
      </subcategory>
    </subcategories>
  </category>
</categories>

Invoking resource :category in the test will provide a Category class. Category is an ActiveResource child which can be used to exercise the /categories end point of the API.

class ApiTest < Test::Unit::TestCase
  resource :category

  def test_categories
    categories = Category.find(:all)
    assert_equal(1, categories.size)
    assert_equal("Music", categories.first.name)
  end

  def test_subcategories
    subcategories = Category.find(:all).first.subcategories
    assert_equal(2, subcategories.size)
    assert_equal("Metal", subcategories[1].name)
  end
  
  def test_category_creation
    Category.create(:name => "Hacking")
    assert_equal(3, Category.find(:all).size)
  end
end