Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Really slow testing with file uploads

I just added validations for a carrierwave image to a model and now tests run really slow. How can I speed up this process? I feel like there must be a better way.


I've been running without validations and used to be able to run through my rspec tests in about 140 seconds, but since i now validate presence of :display_pic I've had to add real file uploads to my project factory. This has upped it to 240 seconds! 140 was already on the heavy side, this is just crazy.

This is how the carrierwave github page recommends setting up Factory Girl:

FactoryGirl.define do
  factory :project do
    display_pic { File.open(File.join(Rails.root, 'spec', 'support', 'projects', 'display_pics', 'test.jpg')) }
  end
end

I made the above test.jpg just an empty text file, so its essentially as small a file as possible.

I also followed the carrierwave recommendation to setup testing:

CarrierWave.configure do |config|
  config.storage = :file
  config.enable_processing = false
end
like image 707
Alex Marchant Avatar asked Mar 08 '12 09:03

Alex Marchant


3 Answers

Are your uploads stored locally or are they going to a cloud service like S3? If the latter, that's probably what's killing your test performance.

For tests, it's always good practice to mock out any external dependencies. If this is your problem, you should use a tool to mock the connection. I think the fog gem comes with built-in support for this.

Update: Regarding the answer by Jefferson Girao, I've used a trick to avoid opening a test file and instead using StringIO to simulate tests files for factory purposes. This performs better because it avoids the disk access:

def test_file_stream(filename = 'test.jpg', mime_type='image/jpg', content = '')
  StringIO.new(content).tap do |s|
    s.content_type = mime_type
    s.original_filename = filename
  end
end
like image 150
Wolfram Arnold Avatar answered Oct 17 '22 20:10

Wolfram Arnold


With validation happening now always that a instance is created the attribute display_pic is accessed and the code inside the brackets

{ File.open(File.join(Rails.root, 'spec', 'support', 'projects', 'display_pics', 'test.jpg')) } 

will be executed (it is lazily executed). This is causing the difference in time.

An option to avoid this is to set to_create for the factory definition what i don't recommend:

FactoryGirl.define do
  factory :project do
    display_pic { File.open(File.join(Rails.root, 'spec', 'support', 'projects', 'display_pics', 'test.jpg')) }

    to_create do |instance|
      instance.save!(:validate => false)
    end 
  end
end
like image 6
jeffersongirao Avatar answered Oct 17 '22 19:10

jeffersongirao


With inspiration from @jeffersongirao and @Wolfram Arnold:

FactoryGirl.define do
  sequence(:image) do |n|
    {
      tempfile: StringIO.new('{・㉨・}'),
      filename: "#{n}.jpeg",
      content_type: 'image/jpeg'
    }
  end

  factory :user do
    avatar { generate(:image) }
  end
end

Two key things here:

  1. CarrierWave's upload setters can make sense of a whole bunch of things. They do it by wrapping what they get in a CarrierWave::SanitizedFile. One of the things it can take is a hash, as here.

  2. CarrierWave::SanitizedFile cares whether the file is empty. I spent way too long wondering why it wouldn't accept my StringIO.new. It needs there to be something in there, but it doesn't care what. I gave it a koala.

like image 3
Peeja Avatar answered Oct 17 '22 20:10

Peeja