Testing carrierwave file uploads with RSpec and FactoryGirl.
TLDR; In this blog post, I am gonna focus only on testing carrierwave. So if you are looking for instruction on installing/setting up carrierwave in your project I would suggest you looking at the carrierwave's wiki over here.
So let's assume we have everything setup, in our rails application, We have a model named Attachment
which uses the Carrierwave uploader(FileUploader
) and the following code within it:
# app/models/attachment.rb
class Attachment < ActiveRecord::Base
mount_uploader :file, FileUploader
end
So to get started with testing, we need to create records which belong to the Attachment. So let's go ahead and create/update our factory to include the file.
# spec/factories/attachments.rb
FactoryGirl.define do
factory :attachment do
photo Rack::Test::UploadedFile.new(File.open(File.join(Rails.root, '/spec/fixtures/myfiles/myfile.jpg')))
end
end
So here I am attaching a file located in my /spec/fixtures/myfiles/
folder as a photo. The above code just attaches the photo lazily to the factory when we build a new one. If you are using create
method and creating records that are actually persisted in the DB, you want to update the above code to:
FactoryGirl.define do
factory :attachment do
after :create do |b|
b.update_column(:photo, "foo/bar/baz.png")
end
end
end
With the above code in our factory, we can use the same for testing with RSpec.
While the above code is enough to get to get started with the specs, I would suggest doing the following things to speed up and optimize your test suites.
- Set storage to local file system in test environment.
- Disable file process in test environment.
- Separate out the upload folders for test environment.
- Clean uploaded files after each request.
Setup Carrierwave to use local storage and disable file processing in test env
We can do that by adding following piece of code to Carrierwave initializer
:
if Rails.env.test? || Rails.env.cucumber?
CarrierWave.configure do |config|
config.storage = :file
config.enable_processing = false
end
end
Separate out the upload folders for test environment.
Next, we should separate test uploads from any other uploads. We can do that by modifying cache_dir
and store_dir
methods for all Carrierwave models (i.e. all models that are descendants of CarrierWave::Uploader::Base).
# config/initializers/carrierwave.rb
CarrierWave::Uploader::Base.descendants.each do |klass|
next if klass.anonymous?
klass.class_eval do
def cache_dir
"#{Rails.root}/spec/support/uploads/tmp"
end
def store_dir
"#{Rails.root}/spec/support/uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}"
end
end
end
Clean uploaded files after each request.
Adding the following code to our spec_helper.rb
will make sure that we don't have stale images hanging around there after each request.
# spec_helper.rb
RSpec.configure do |config|
config.after(:each) do
if Rails.env.test? || Rails.env.cucumber?
FileUtils.rm_rf(Dir["#{Rails.root}/spec/support/uploads"])
end
end
end
Settting asset_host
You also would want to set the asset_host option for the carrierwave. For that add the following lines to the initializer.
CarrierWave.configure do |config|
config.asset_host = ActionController::Base.asset_host
end
So this is how your carrierwave initializer would like finally:
if Rails.env.test? || Rails.env.cucumber?
CarrierWave.configure do |config|
config.storage = :file
config.enable_processing = false
end
# make sure uploader is auto-loaded
FileUploader
CarrierWave::Uploader::Base.descendants.each do |klass|
next if klass.anonymous?
klass.class_eval do
def cache_dir
"#{Rails.root}/spec/support/uploads/tmp"
end
def store_dir
"#{Rails.root}/spec/support/uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}"
end
end
end
end
CarrierWave.configure do |config|
config.asset_host = ActionController::Base.asset_host
end
Hope I have detailed enough pointers here to get started with carrierwave testing.