Tuesday, February 22, 2011

Testing drama when using Mongoid, S3, Paperclip, and RSpec

The team is in the early stages of writing a new CMS in Rails 3 to support our online publications. We've encountered a few interesting hurdles in our first Iteration, one of those coming in the form of a Mongoid/S3/Paperclip cocktail and how to test it.

Paperclip & Mongo

Thotbott has done it's due diligence and released Paperclip with packaged shoulda matchers. However ... since we are using Mongo through Mongoid we needed to introduce Mongoid-Paperclip into our project. Unfortunately this does cause problems with the shoulda matchers provided by Paperclip. In particular the should have_attached_file and should validate_attachment_content_type fail to work out of the box.

Should have_attached_file

We were already using mongoid-spec to test our Mongo documents and we decided to use that to our advantage. After inspecting the paperclip matcher implementation we discovered they essentially were checking for the existence of paperclip defined attributes on the class using the respond_to? method. This doesn't work so well with Mongo, so we followed the matchers pattern and created our own custom matcher that leverages mongo-spec's matchers. Here is the result:

RSpec::Matchers.define :have_mongoid_attached_file do |expected|
  match do |actual|
    have_fields(":#{actual}_content_type", ":#{actual}_file_name", ":#{actual}_file_size", ":#{actual}_updated_at")
  end
end

should validate_attachment_size

Interestingly enough, this worked fine. Win!

should validate_attachment_content_type

This however was not a Win, but a big Fail. validates_attachment_content_type works, but the matcher does not. We time boxed getting the matcher to work and failed to complete it in the time allowed. This remains an open item at the moment.

S3

Configuring our paperclipped class to use S3 was easy!! We added our s3.yml, told the class how to find it and to use S3. It wasn't long before we noticed the specs were taking 1.5 seconds longer to run; the 67 previously written specs ran in under 0.3 seconds. The only change we made was configuring S3.

After some serious inspection and several four letter words we discovered even our have_mongoid_attached_file was causing the app to establish a connection to S3!! We tried bringing in FakeWeb, mocking, stubbing, and more mocking and stubbing before the lightning struck ... remove S3 configuration from our test environment. Our paperclip declaration now looks like this:

  has_mongoid_attached_file :data, {
    :styles => {
      :thumb => "75x80>",
      :medium => "120x120>" },
    :path => "photos/:date/:id/:style/:filename"
  }.merge(::PAPERCLIP_CONFIG)

In our application.rb we've defined PAPERCLIP_CONFIG as:
    PAPERCLIP_CONFIG = {
      :storage => :s3,
      :s3_credentials => "#{Rails.root}/config/s3.yml"
    }
and we override that in our test.rb with a simple:
    PAPERCLIP_CONFIG = {}

Now our specs are running sub second again and there was peace without complicated mocking.

Note to self => We're looking to integrate the shoulda matchers for mongoid into Paperclip itself in a way that is agnostic of the database being used. IMO, it sucks that Paperclip is coupled to ActiveRecord and we've had to introduce monkey patches in the form of gems to override that coupling.

1 comment:

  1. PAPERCLIP_CONFIG = {} is a really great idea, thank you. I have a get_image helper function, I modified it to cache the image in a variable to improce rspec performace. Thanks for sharing

    ReplyDelete