/ rails

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.

  1. Set storage to local file system in test environment.
  2. Disable file process in test environment.
  3. Separate out the upload folders for test environment.
  4. 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.

Manu S Ajith

Manu S Ajith

Tech Entrepreneur, dating Elixir, in long-term ❤️ w/ Ruby, had multiple one night stands w/ Go. Into functional paradigms DDD/CQRS/EventSourcing architecture these days. @manusajith on the interwebs

Read More