However, there are some quirks that are required to make ActiveResource happy. For example, when you are doing a ‘create’ or ‘update’ request, ActiveResource wants the response location to point to the ‘show’ URL for the new or updated record. For example, here’s an ActiveResource ‘create’ call:
new_story = Story.create( :name => "New Story", :requested_by => "Dan", :description => "Make API ActiveResource compliant")
On the controller, you must add the :location option to the render – you can’t redirect:
render :xml => xml, :location => service_project_story_url(service_id, project_id, @story), :status => status
…otherwise, you get this helpful error from ActiveResource:
/Library/Ruby/Gems/1.8/gems/activeresource-2.2.2/lib/active_resource/base.rb:1006 :in `id_from_response': undefined method `' for nil:NilClass (NoMethodError) from /Library/Ruby/Gems/1.8/gems/activeresource-2.2.2/lib/active_resource/base.rb:993:in `create'
This is the type of error which you will only catch through end-to-end testing with a real ActiveResource client hitting the running app. When I did the initial spike to see what problems we would run into, I wrote a simple manual script to run against the local development environment, hacking my way to a point which didn’t blow up and I could visually inspect the output:
#!/usr/bin/env ruby require 'rubygems' require 'activeresource' require 'pp' class Story < ActiveResource::Base self.site = "http://localhost:3000/services/v1/projects/1" headers['TOKEN'] = '6cfc2055d1df5605241759014b06b232' end p "========================== Stories#create =====================================" new_story = Story.create(:name => "New Story", :requested_by => "Dan", :description => "Make API ActiveResource compliant") pp new_story # etc for all other supported API actions...
However, now that we are doing the real non-spike implementation, we want to automate this end-to-end integration testing as part of our Continuous Integration. That way, we’ll ensure that we are fully ActiveResource-compliant (against current and future versions), and that we don’t have any inadvertent regressions due to future API bugfixes/enhancements.
Digging through the internets and rubyonrails-talk list archives turns up some discussion, but no good answers:
- Thoughtbot blog post on ActiveResource and Testing
- rubyonrails-talk post on “Testing XML over HTTP in a Rails app”
- rubyonrails-talk post on “Testing ActiveResource models with HttpMock”
All of these mention using ActiveResource::HttpMock. However, as Eric and Xavier point out, there seem to be drawbacks to this approach. Plus, even if we get it to work, I’m worried the usage of HttpMock might mask some other issues related to authentication handling, or who knows what else. That’s what real integration tests are for. Finally, HttpMock is an undocumented internal method that seems to exist in order to support Rails’ test suite, so it’s probably not a great idea to depend on that long term.
So, we don’t have a great answer yet, but it seems clear that the highest-value, least-risk approach is to hit a real running app over HTTP with a real ActiveResource client.
The current plan is to leverage our existing Selenium RC test environment, which already has support for spinning up and managing a Rails server with the test environment. We can then port the manual spike tests above to automated ones which run as part of the selenium suite under Continuous Integration, and add appropriate assertions. This isn’t optimal, though, because they won’t actually use Selenium RC at all, which may confuse people. However, there’s no sense reinventing the wheel (and adding time to the overall CI build) by spinning up a separate test server instance when we already spin one up for our selenium suite.
Let us know if you have any clever solutions.