Overriding Paperclip S3 Storage when Testing with RSpec and Cucumber
I have a fairly comprehensive test suite for MobManager that uses both Cucumber and RSpec tests. MobManager also uses Thoughtbot’s Paperclip for file and attachments, using Amazon S3 in production. While Paperclip and S3 are great in production, it’s less than ideal to need an internet connection to run tests that involve models with paperclip attachments. The MobManager test suite takes about 4 minutes to run on my MBP, and I assumed that writing the attachments to disk locally would speed up my tests and allow me to run my tests untethered from the internet.
After Googling around and trying various attempts to stub and mock the S3 calls unsuccessfully, I rolled up my sleeves and came up with this solution. Please let me know if you’ve got a better one.
In previous projects using attachment_fu, I used to do some if/then checking before defining the attachment and migrations. Doing so with Paperclip would have resulted in something like the following: (pseudocodeish)
class Photo < ActiveRecord::Base if Rails.root == 'production' has_attached_file :attachment, :storage => :s3 # other options here else has_attached_file :attachment # defaults to file system end end
But this always seemed like an ugly solution. While I was looking at Paperclip’s own S3 cucumber tests for inspiration, I saw that their tests were redefining the has_attached_file for their models. Nice!
Override the call to Paperclip by overriding your model:
# Rails.root/spec/support/photo.rb # BTW, /spec/support is automatically picked up by spec_helper.rb class Photo < ActiveRecord::Base has_attached_file :attachment # this will now use the file system in testing end
This allows me to override the Paperclip settings without having to pollute my model with the ugly if/then scenario. Awesome.
To get the same behavior in my Cucumber tests, I copied this line out of spec_helper.rb into my Cucumber env.rb (/features/support/env.rb).
Dir[Rails.root.join("spec/support/**/*.rb")].each {|f| require f}
Close but not quite
Some of my cucumber tests were failing after making this change. Long story short, there’s some kind of garbage collection/temp file race condition, and my model would save before writing the file to disk, and eventually causing my tests to fail.
I found the flush_writes method in the Paperclip storage adapter code and added this hack to forcefully flush the cash when saving the model.
class Photo < ActiveRecord::Base has_attached_file :attachment after_save :write_attachment private def write_attachment if attachment attachment.flush_writes end end end
Hooray. All tests pass!
Paperclip doesn’t have an option to write the files to the database, so these new attachments will be written to your Rails.root/public/system directory. Don’t forget to update your .gitignore to exclude these files.
Unfortunately, this didn’t improve the test suite performance at all, but at least I can run the test suite when I don’t have an internet connection.
