Rails carrierwave testing - how to remove file after test? - ruby-on-rails

I am testing the carrierwave upload functionality using rspec and capybara. I have something like:
describe "attachment" do
let(:local_path) { "my/file/path" }
before do
attach_file('Attachment file', local_path)
click_button "Save changes"
end
specify {user.attachment.should_not be_nil}
it { should have_link('attachment', href: user.attachment_url) }
end
And this works great. The problem is that after testing the uploaded image remains in my public/uploads directory. How can I remove it after the test is done? I tried something like this:
after do
user.remove_attachment!
end
but it did not work.

You're not the only one having issues to delete the files in carrierwave.
I ended up doing:
user.remove_attachment = true
user.save
I got this tip reading this.

A cleaner solution that seems to work for me is the following in spec/support/carrierwave.rb:
uploads_test_path = Rails.root.join('uploads_test')
CarrierWave.configure do |config|
config.root = uploads_test_path
end
RSpec.configure do |config|
config.after(:suite) do
FileUtils.rm_rf(Dir[uploads_test_path])
end
end
This would set the whole root folder specific to the test environment and delete it all after the suite, so you do not have to worry about store_dir and cache_dir separately.

Ha! I found the answer to this today.
The auto-removal of the downloaded file is done in an after_commit hook.
These do not get run by default in rails tests. I never would have guessed that.
It is however documented offhandedly in a postscript note here:
http://api.rubyonrails.org/classes/ActiveRecord/Transactions/ClassMethods.html#method-i-after_commit
I discovered this by deep diving into the carrierwave code with a
debugger and just happened to notice it in the comments above the
source code to after_commit when I stepped into it.
Thank goodness ruby libraries are not stripped of comments at runtime like JS. ;)
The workaround suggested in the docs is to include the 'test_after_commit'
gem in your Gemfile BUT ONLY IN THE TEST ENVIRONMENT.
i.e.
Gemfile:
...
gem 'test_after_commit', :group => :test
...
When I did this, it completely solved the problem for me.
Now, my post-destruction assertions of cleanup pass.

The latest CarrierWave documentation for this technique is as follows:
config.after(:suite) do
if Rails.env.test?
FileUtils.rm_rf(Dir["#{Rails.root}/spec/support/uploads"])
end
end
Note that the above simply assumes youre using spec/support/uploads/ for images and you dont mind deleting everything in that directory. If you have different locations for each uploader, you may want to derive the upload and cache directories straight from the (factory) model:
config.after(:suite) do
# Get rid of the linked images
if Rails.env.test? || Rails.env.cucumber?
tmp = Factory(:brand)
store_path = File.dirname(File.dirname(tmp.logo.url))
temp_path = tmp.logo.cache_dir
FileUtils.rm_rf(Dir["#{Rails.root}/public/#{store_path}/[^.]*"])
FileUtils.rm_rf(Dir["#{temp_path}/[^.]*"])
end
end
or, if you want to delete everything under the CarrierWave root that you set in an initializer, you can do this:
config.after(:suite) do
# Get rid of the linked images
if Rails.env.test? || Rails.env.cucumber?
FileUtils.rm_rf(CarrierWave::Uploader::Base.root)
end
end

Related

rails - files in autoload_paths folder aren't loaded

I have a app/extensions folder where my custom exceptions reside and where I extend some of the Ruby/Rails classes. Currently there are two files: exceptions.rb and float.rb.
The folder is specified in the ActiveSupport::Dependencies.autoload_paths:
/Users/mityakoval/rails/efo/app/extensions/**
/Users/mityakoval/rails/efo/app/assets
/Users/mityakoval/rails/efo/app/channels
/Users/mityakoval/rails/efo/app/controllers
/Users/mityakoval/rails/efo/app/controllers/concerns
/Users/mityakoval/rails/efo/app/extensions
/Users/mityakoval/rails/efo/app/helpers
/Users/mityakoval/rails/efo/app/jobs
/Users/mityakoval/rails/efo/app/mailers
/Users/mityakoval/rails/efo/app/models
/Users/mityakoval/rails/efo/app/models/concerns
/Users/mityakoval/rails/efo/app/template.xlsx
/Users/mityakoval/.rvm/gems/ruby-2.4.1#web_app/gems/font-awesome-rails-4.7.0.2/app/assets
/Users/mityakoval/.rvm/gems/ruby-2.4.1#web_app/gems/font-awesome-rails-4.7.0.2/app/helpers
/Users/mityakoval/rails/efo/test/mailers/previews
The reason for it to be listed there twice is that it should be automatically loaded since it was placed under app directory and I have also manually added it to the autoload_paths in application.rb:
config.autoload_paths << File.join(Rails.root, 'app', 'extensions/**')
The strange thing is that my exceptions.rb is successfully loaded at all times, but the float.rb isn't unless eager loading is enabled.
Answers to this question say that it might be related to Spring (which I tend to believe), so I've added the folder to spring.rb:
%w(
.ruby-version
.rbenv-vars
tmp/restart.txt
tmp/caching-dev.txt
config/application.yml
app/extensions
).each { |path| Spring.watch(path) }
I've restarted Spring and the Rails server multiple times and nothing worked. Does anyone have any suggestions?
Ruby version: 2.4.1
Rails version: 5.1.5
EDIT
/Users/mityakoval/rails/efo/app/extensions/float.rb:
class Float
def comma_sep
self.to_s.gsub('.', ',')
end
end
rails console:
irb> num = 29.1
irb> num.comma_sep
NoMethodError: undefined method `comma_sep' for 29.1:Float
from (irb):2
A better way to monkeypatch a core class is by creating a module and including it in the class to be patched in an initializer:
# /lib/core_extensions/comma_seperated.rb
module CoreExtensions
module CommaSeperated
def comma_sep
self.to_s.gsub('.', ',')
end
end
end
# /app/initializers/core_extensions.rb
require Rails.root.join('lib', 'core_extensions', 'comma_seperated')
# or to require all files in dir:
Dir.glob(Rails.root.join('lib', 'core_extensions', '*.rb')).each do |f|
require f
end
Float.include CoreExtensions::CommaSeperated
Note that here we are not using the Rails autoloader at all and explicitly requiring the patch. Also note that we are placing the files in /lib not /app. Any files that are not application specific should be placed /lib.
Placing the monkey-patch in a module lets you test the code by including it in an arbitrary class.
class DummyFloat
include CoreExtensions::CommaSeperated
def initialize(value)
#value = value
end
def to_s
#value.to_s
end
end
RSpec.describe CoreExtensions::CommaSeperated do
subject { DummyFloat.new(1.01) }
it "produces a comma seperated string" do
expect(subject.comma_sep).to eq "1,01"
end
end
This also provides a much better stacktrace and makes it much easier to turn the monkey patch off and on.
But in this case I would argue that you don't need it in the first place - Rails has plenty of helpers to humanize and localize numbers in ActionView::Helpers::NumberHelper. NumberHelper also correctly provides helper methods instead of monkeypatching a core Ruby class which is generally best avoided.
See:
3 Ways to Monkey-Patch Without Making a Mess

Rails 4 Paperclip FactoryGirl file uploading

I have a FactoryGirl :product factory that uses fixture_file_upload to set image, which is a Paperclip attachment.
image { fixture_file_upload "#{Rails.root}/spec/fixtures/images/product.png", 'image/png' }
fixture_file_upload works fine, but every time a test creates a new Product using the factory, Paperclip creates a new file in publicproducts/<id>/original.png. This is the issue.. Filling a the folder publicproducts on each test run is not acceptable.
The first workaround I can think of is the solution mentioned in https://github.com/carrierwaveuploader/carrierwave/wiki/How-to:-Cleanup-after-your-Rspec-tests
Have you solved this problem in another way?
The solution, also mentioned by Deep is to:
specify that paperclip in test environment should upload files to folder test_uploads,
modify factory_girl factories to upload a fixture from ex. spec/fixtures/images/filename.extension,
add an after all cleanup block in rails_helper.rb
In code:
config/environments/test.rb
...
config.paperclip_defaults = {
path: ':rails_root/test_uploads/:class/:id/:attachment/:filename.:extension',
url: ':rails_root/test_uploads/:class/:id/:attachment/:filename.:extension'
}
...
spec/factories/products.rb
image { fixture_file_upload "#{Rails.root}/spec/fixtures/images/product.png", 'image/png' }
rails_helper.rb
...
include ActionDispatch::TestProcess
config.after(:all) do
if Rails.env.test?
test_uploads = Dir["#{Rails.root}/test_uploads"]
FileUtils.rm_rf(test_uploads)
end
end
...

How to get the current test filename from RSpec?

I'm trying to speed up a large RSpec project's tests. In addition to using RSpec's --profile option I wanted to get the longest running test files [1] printed out.
In my spec_helper.rb I dump the classes being tested and total time to a file, however as we have spec/model and spec/request directories I'd really like to be able to print the current test's filename and not just the class name (described_class), so that the user can disambiguate between model/foo_spec.rb and request/foo_spec.rb when optimizing.
In a before block in the spec/spec_helper.rb, how can I get the current test file's filename?
My (heavily trimmed) spec_helper looks like this:
config.before :all do
#start_time = Time.now
end
config.after :all do |test|
timings.push({ :name => test.described_class,
:file => 'I do not know how to get this',
:duration_in_seconds => (Time.now - #start_time) })
end
config.after :suite do
timing_logfile_name = 'log/rspec_file_times.log'
timing_logfile = "#{File.dirname(__FILE__)}/../#{timing_logfile_name}"
file = File.open(timing_logfile, 'w')
timings.sort_by{ |timing| timing[:duration_in_seconds].to_f }.reverse!.each do |timing|
file.write( sprintf("%-25.25s % 9.3f seconds\n",
timing[:name], timing[:duration_in_seconds]) )
end
file.close
tell_if_verbose("Overall test times are logged in '#{timing_logfile_name}'")
end
This doesn't seem to be available in the curretn RSpec meta-data, but I'm hoping someone more familiar with the internals can think of a way to expose it. Thanks,
Dave
[1] Often a file with, say, 100 examples in it yields more speed up than a single example from --profile - when that large file's before :each / before :all blocks are targetted, obviously even a ms saved is multiplied up by the number of tests in the file. Using this technique in addition to --profile helped me a lot.
As long as you're just using this for profiling your tests to identify which files need to be improved, you should be able to toss this into your spec_helper.rb file (and remove it afterwards). I fully understand that this is not pretty/clean/elegant/acceptible in production environments and I disavow that I ever wrote it :)
config.before(:each) do |example|
path = example.metadata[:example_group][:file_path]
curr_path = config.instance_variable_get(:#curr_file_path)
if (curr_path.nil? || path != curr_path)
config.instance_variable_set(:#curr_file_path, path)
puts path
end
end

AssociationTypeMismatch and FactoryGirl

This has been causing some frustration recently...
It seems that using Factories in my cucumber tests, in some situations causes AssociationTypeMismatch errors such as:
MyModel(#65776650) expected, got MyModel(#28190030) (ActiveRecord::AssociationTypeMismatch)
These seem to happen when there is an association reference - as if the Factory created object is different to the real one. See this question for more details: Cucumber duplicate class problem: AssociationTypeMismatch
I have been gradually changing Factory calls to real Model.create or mock_model calls. It would be nice to keep using Factory girl... I wonder if there is something I may have done wrong?
Thank you
I had this happening with me on Rails 3.1.0 rc5, and got it working.
To expand on Jonas' answer.
You should change your Gemfile to be like this:
gem 'factory_girl', '~> 2.0.0', :require => false
gem 'factory_girl_rails', '~> 1.1.0', :require => false
And then if you are using Spork, make your spec/spec_helper.rb file look like this:
Spork.each_run do
require 'factory_girl'
require 'factory_girl_rails'
end
It seems to happen if ActiveSupport unloads and reloads a constant that you have a reference to.
I've experienced the same with Rspec/Capybara, and what helped was a mixture of different things:
Make sure you have cached_classes set to false in your test environment (config/environments/test.rb)
In your gemspec, try replacing require 'factory_girl_rails' with 'factory_girl'
I'm using Spork (a test server), which seems to make this stuff increasingly difficult.
If you are using a test server, evaluate whether you should put ', :require => false' after factory_girl in your gemspec.
The topic is also covered in this google groups thread
Please let us know if any of this helped.
If you're using Spork, make sure to reload your factories after reloading your models.
E.g.
Spork.each_run
if Spork.using_spork?
print "Reloading models ... "
ActiveSupport::Dependencies.clear
puts "done"
print "Reloading factories ... "
FactoryGirl.reload
puts "done"
end
end
This happens because cache_classes is false, as is required by Spork. Capybara reloads Rails classes for every request (or, to be correct, Rails' reloader middleware does, which is not called for normal tests), and this freaks out the factories (exactly why, I'm not sure). You can either reload them, or simply run your Capybara specs outside of Spork.
So you need two things: to only run Capybara outside of Spork, and to set cache_classes to false only for Spork.
To only run Capybara outside of Spork, I have a Guardfile that runs specs in spec/requests outside of Spork and other specs inside of Spork here:
https://gist.github.com/1731900
Then, in config/environments/test.rb:
config.cache_classes = !ENV['DRB']
Your Capybara specs will be a bit slower, as they need to boot rails, but everything will Just Work.
I had some success with reloading the Factory definitions try something like this:
class Factory
def self.reload_definitions #:nodoc:
self.factories.clear
definition_file_paths.each do |path|
load("#{path}.rb") if File.exists?("#{path}.rb")
if File.directory? path
Dir[File.join(path, '*.rb')].each do |file|
load file
end
end
end
end
end
I ran into this issue when I passed the "class" option to my factory that was inherited by other factories:
factory :draft_resource, :class => Resource do
factory :resource, :parent => :draft_resource do
The only solution I could find was to simply not do this.
I ran into this same issue and spent probably ten hours trying every solution on this thread and everywhere else on the web. I started ripping out huge chunks of code trying to get it as close to another app of mine in which I couldn't reproduce the problem. Finally, I came across some helper functions in my spec_helper file:
def sign_in(user)
visit signin_path
fill_in "Email", with: user.email
fill_in "Password", with: user.password
click_button "Sign in"
# Sign in when not using Capybara as well.
cookies[:remember_token] = user.remember_token if defined?(cookies)
end
A sign_in helper intended to work both in controller and request specs. And it does, sort of--just not with spork. When I removed the capybara helpers the issue was resolved:
def sign_in(user)
cookies[:remember_token] = user.remember_token
end

Delete a folder after the cucumber scenario

I have 2 cucumber scenarios that simulate paperclip image upload. I want to remove those folders again once scenarios are complete.
I have the following attachment folder structure:
:url => "/system/:attachment/:listing_id/:id/:style_:filename"
Paperclip automatically deletes the :id/:style_:filename folder but not the parent folder.
I have a the following in my listings controller (1 listing has many images) which works great to remove the image folder with the listing id when the listing is deleted. I need to simulate the same in Cucumber after the step is run.
def destroy
#listing = Listing.find(params[:id])
# if destroy was a success, remove the listing image folder
if #listing.destroy
end
require 'fileutils'
dir = Rails.root + '/system/photos/' + #listing.id.to_s()
FileUtils.rm_rf(dir)
respond_to do |format|
format.html { redirect_to(listings_url) }
format.xml { head :ok }
end
end
I could a) tell cucumber to delete the :listing_id folder name after running through the scenario or b) tell cucumber to delete the listing as the final step?
I've tried adding this to my cucumber env.rb file:
AfterStep('#paperclip') do
# This will only run before steps within scenarios tagged
# with #cucumis AND #sativus.
# delete folders that were created with paperclip during the test
require 'fileutils'
##listing.id = 55
#dir = Rails.root + '/system/photos/' + #listing.id.to_s()
dir = Rails.root + '/system/photos/55' # NOT WORKING
FileUtils.rm_rf(dir)
end
But that causes problems because 1) I don't know how to get the #listing.id from that scenario, and 2) even when I hardcode it (as above) it doesn't remove it.
Any thoughts?
Already a bit older, but as I just ran over the same issue myself here is what I did:
dir = Rails.root + 'images/'
dir.rmtree if dir.directory?
# or the short form, if you know the directory will be there
(Rails.root + 'images/').rmtree
So I guess the problem was your '/' at the beginning of the folder. At least for me it didn't work with that slash.
I would leave a comment, but don't have the points (or so I would assume) to do so.
There really is no reason that this shouldn't work. Have you confirmed that FileUtils.rm_rf(dir) does in fact remove the directory in the test environment?
You can test this in 'script/console test'.
You can hook into Cucumber's at_exit to remove any folders you like. I'm using the attached code in features/support/uploads_cleaner.rb.
# Removes uploaded files when all scenarios for the current test process
# are finished. Ready for parallel_tests, too.
require 'fileutils'
at_exit do
directory_name = "#{ Rails.env }#{ ENV['TEST_ENV_NUMBER'] }"
uploads_path = Rails.root.join('public/system', directory_name)
FileUtils.remove_dir(uploads_path) if uploads_path.directory?
end
Reposted from this makandra Card.

Resources