Tuesday, November 16, 2010

Faking Paperclip S3 calls with Fakeweb

I recently wrote Cucumber acceptance tests for a project feature that involved uploading images to Amazon S3 via the Ruby Paperclip library. At first, I had them actually hit S3 for real to drive the implementation correctly without any mocking. Over time though, the tests became really slow and fragile because of their dependency on the web service, so I used Fakeweb to fake the upload requests to S3.

Here is the Cucumber step I wrote for that:

When /^(?:|I )attach the image "([^\"]*)" to "([^\"]*)" on S3$/ do |file_path, field|
definition = Image.attachment_definitions[:attachment]
path = "http://s3.amazonaws.com/#{definition[:bucket]}/#{definition[:path]}"
path.gsub!(':filename', File.basename(file_path))
path.gsub!(/:([^\/\.]+)/) do |match|
FakeWeb.register_uri(:put, Regexp.new(path), :body => "OK")
When "I attach the file \"#{file_path}\" to \"#{field}\""

That prepares the environment for receiving an S3 request for upload, so when the test reaches the Cucumber step for uploading the image (And I press "Upload") the environment can receive the request from the Paperclip-enhanced class (Image) and fake a response for it.

To make that step work, make sure to configure your project with the "fakeweb" gem.

Here is how Paperclip was configured in the Image class:

class Image < Attachment
validates_attachment_content_type :attachment, :content_type => ["image/jpg", "image/jpeg", "image/png", "image/gif"], :if => :attachment_file_name
has_attached_file :attachment,
:storage => :s3,
:styles => {
:medium => "300x300>"
:s3_credentials => "#{RAILS_ROOT}/config/environments/#{RAILS_ENV}/amazon_s3.yml",
:path => "#{RAILS_ENV}/images/:id/:style/:filename",
:bucket => "bucketname",
:url => ':s3_domain_url',
:whiny => false

Fakeweb ended up cutting about 30 seconds off the time to run all Cucumber tests. Good deal!

1 comment:

Tom Kersten said...

Nice write-up, Andy.