Unit testing paperclip uploads with Rspec (Rails) - ruby-on-rails

Total Rspec noob here. Writing my first tests tonight.
I've got a model called Image. Using paperclip I attach a file called photo. Standard stuff. I've run the paperclip generator and everything works fine in production and test modes.
Now I have a spec file called image.rb and it looks like this (it was created by ryanb's nifty_scaffold generator):
require File.dirname(__FILE__) + '/../spec_helper'
describe Image do
it "should be valid" do
Image.new.should be_valid
end
end
This test fails and I realise that it's because of my model validations (i.e. validates_attachment_presence)
The error that I get is:
Errors: Photo file name must be set., Photo file size file size must be between 0 and 1048576 bytes., Photo content type is not included in the list
So how do I tell rspec to upload a photo when it runs my test?
I'm guessing that it's got somethign to do with fixtures.... maybe not though. I've tried playing around with them but not having any luck. For the record, I've created a folder called images inside my fixtures folder and the two files I want to use in my tests are called rails.png and grid.png)
I've tried doing the following:
it "should be valid" do
image = Image.new :photo => fixture_file_upload('images/rails.png', 'image/png').should be_valid
# I've also tried adding stuff like this
#image.stub!(:has_attached_file).with(:photo).and_return( true )
#image.stub!(:save_attached_files).and_return true
#image.save.should be_true
end
But rspec complains about "fixture_file_upload" not being recognised... I am planning to get that Rspec book. And I've trawled around the net for an answer but can't seem to find anything. My test database DOES get populated with some data when I remove the validations from my model so I know that some of it works ok.
Thanks in advance,
EDIT:
images.yml looks like this:
one:
name: MyString
description: MyString
two:
name: MyString
description: MyString

This should work with Rails 2.X:
Image.new :photo => File.new(RAILS_ROOT + '/spec/fixtures/images/rails.png')
As of Rails 3, RAILS_ROOT is no longer used, instead you should use Rails.root.
This should work with Rails 3:
Image.new :photo => File.new(Rails.root + 'spec/fixtures/images/rails.png')
Definitely get the RSpec book, it's fantastic.

Rails.root is a pathname object so you can use it like this:
Image.new :photo => Rails.root.join("spec/fixtures/images/rails.png").open
Edit - probably does not work in Rails 3...
see answer by #Paul Rosania

In case anyone else finds this via Google, RAILS_ROOT is no longer valid in Rails 3.0. That line should read:
Image.new :photo => File.new(Rails.root + 'spec/fixtures/images/rails.png')
(Note the lack of leading slash!)

I use the multipart_body gem in my integration tests. Its a bit truer to BDD than testing.
http://steve.dynedge.co.uk/2010/09/19/multipart-body-a-gem-for-working-with-multipart-data/
With respect to rspec and paperclip, the has_attached_file :photo directive creates a virtual attribute of sorts i.e. :photo ... when you assign a file or a path to photo, paperclip takes over, stores the file, optionally does processing on it e.g. auto-create thumbnails, import a spreadsheet, etc. You aren't telling rspec to test paperclip. You are invoking code and telling rspec what the results of that code -should- be.
In $GEM_HOME/gems/paperclip-2.3.8/README.rdoc, about 76% of the way through the file under ==Post Processing (specifically lines 147 and 148):
---[ BEGIN QUOTE ]---
NOTE: Because processors operate by turning the original attachment into the styles, no processors will be run if there are no styles defined.
---[ END QUOTE ]---
Reading the code, you'll see support :original ... does your has_attached_file define a style?
I use a generic ":styles => { :original => { :this_key_and => :this_value_do_not_do_anything_unless_a_lib_paperclip_processors__foo_dot_rb__does_something_with_them } }" ... just to get paperclip to move the file from some temp directory into my has_attached_file :path
One would think that would be default or more obvious in the docs.

Related

Ruby on Rails Fixtures in sub-directories

I'm using Rails unit testing and fixtures framework. Unless there's a configuration I haven't seen, I can't use the "Advanced Fixtures" (no id objects) from fixtures in sub-directories:
fixtures/people.yml
_fixture:
model_class: Person
myself:
first_name: Me
last_name: Myself
The following call will pass as expected:
fixtures :people
assert(people(:myself)))
while this one will not (after I move people.yml to subdir):
fixtures "subdir/people"
assert(people(:myself)))
In the later case, the error I get is this:
NoMethodError: undefined method `people'
Using Advanced Fixtures seem valuable but having all of my fixture files in the root of /fixtures seems missing something. I have a few test files and I'd like to have various tests use different fixtures directories.
Any input will be appreciated.
I would try saving people.yml in /fixtures/subdir/people.yml. The documentation is here https://apidock.com/rails/ActiveRecord/TestFixtures/ClassMethods/set_fixture_class .
test_helper.rb
set_fixture_class :people => 'Subdir::People'
in your test file.
before do
myself = subdir_people(:myself)
register(myself)
end
it "should test myself" do
assert(people(:myself)))
end
Here is my final solution:
In the fixture file, I kept:
_fixture:
model_class: Person
In the test file, I got:
def people(sym)
subdir_people(sym)
end
def test_myself
assert(people(:myself))
end
That way, it saves me from refactoring my numerous people() calls. Or I can use subdir_people() if I want.

Testing Carrierwave Uploads with Minitest

Just trying to add some basic tests to my Carrierwave Uploader. I am starting with the default generated tests and trying to go from there. I am getting a weird error and not sure where to go from here.
I have a polymorphic Upload model which has:
mount_uploader :file, DocumentUploader
My DocumentUploader has something like this:
def store_dir
"#{model.uploadable_type.downcase.pluralize.underscore}/#{model.parent_asset.id}/uploads/#{model.id}/"
end
This makes my store directories look like:
/locations/24/uploads/56
when I run the default tests for example:
test "should destroy upload" do
assert_difference('Upload.count', -1) do
delete :destroy, id: #upload
end
assert_redirected_to uploads_path
end
I get:
ERROR["test_should_destroy_upload", UploadsControllerTest,1.9893769259997498]
test_should_destroy_upload#UploadsControllerTest (1.99s)
NoMethodError: NoMethodError: undefined method `id' for nil:NilClass
app/uploaders/document_uploader.rb:34:in `store_dir'
In my Uploads fixtures I have the polymorphic associates set etc. Themodel is not being set to the Uploadable association in the DocumentUploader i.e. it's nil.
All my other tests work so far and my uploader works fine in production and development etc. I am sure I am missing something trivial in the set-up or it's a Carrierwave specific issue.
Chiming in really late here.
Using fog with carrierwave I have found to be a drag. carrierwave-aws gem is non-negotiable for me.
The Read Me provides a quick, overall stub to add to the uploads.rb configuration file:
config.aws_credentials = {
[...], # Required
stub_responses: Rails.env.test? # Optional, avoid hitting S3 actual during tests
}
For the presentation layer, I find this acceptable because, you often want to see results, and particularly the versions of such results. Once those run properly, they are hard to break if the method does not change - and those are rather static methods.

How to "automate" Paperclip with Ruby on Rails?

I have a client who wants to bulk-upload photo files to her existing Ruby on Rails application that I developed. I've scratched together code and scripts to unzip a zip file and rename all included files sequentially:
photo0001.png, photo0002.png etc...
However, I'm stuck on how to write a model or method to go through and assign each photo to a new instance, as paperclip defines the following:
class Picture
has_attached_file :photo
So I'm looking for a way to do something like this:
for i in 1..5
Picture.create(:caption => "Test", :photo => "photo/photo000#{i}.png")
`rm photo/photo000#{i}.png`
end
I'm sure there's more to it than that, but that's the basic idea...
I have a rake task for uploading photos, the meat of it looks like this:
Dir.glob(photo_path).entries.each do |e|
puts "Uploading #{e}"
Picture.create!(:photo => File.open(e))
end
I don't know if this helps, but it should hopefully point you in the right direction.

Stubbing Paperclip S3 requests in specs

I am using Paperclip and S3 for image uploads and am trying to stub out calls to S3 from my test suite. I found the thoughtbot post which mentions doing
a.cover { a.paperclip_fixture('album', 'cover', 'png') }
but that gives me a "wrong number of arguments (4 for 2)" error. I tried switching the arguments above to an array, which removes the original error, but gives an error saying "Attribute already defined: paperclip_fixture".
Has anyone been able to get this working? Also, I'd ideally like to use the local filesystem for the development environment. Is there an easy way to do this?
Okay, I've got the basic issue figured out. This is (I believe) as Eliza said, because I'm not using shoulda (I'm using rspec 2.6.0 and factory_girl 2.1.2).
Here's what worked for me (where Profile is the class that has attachements):
Profile.any_instance.stub(:save_attached_files).and_return(true)
#profile = Factory(:profile)
At the moment I just have this right in my before method of my rspec example. There's probably a better place to put it.
With latest paperclip(from github master branch) and aws-sdk version 2, I solved my issue with following configuration :
require "aws-sdk"
Aws.config[:s3] = {stub_responses: true}
For more information, please take a look at amazon sdk
Placing this in my 'spec/rails_helper.rb' file worked for me:
require 'aws'
AWS.stub!
AWS.config(:access_key_id => "TESTKEY", :secret_access_key => "TESTSECRET")
Are you using shoulda? If you aren't using shoulda the paperclip_fixture method that you're using may come from somewhere else and thus behave differently.
Potentially relevant: https://github.com/thoughtbot/paperclip/blob/master/shoulda_macros/paperclip.rb
Many of these techniques don't seem to work with the latest paperclip and S3. What finally worked for me is the combination of:
AWS.config(:access_key_id => "TESTKEY", :secret_access_key => "TESTSECRET", :stub_requests => true)
and
Mymodel.any_instance.stubs(:save_attached_files).returns(true)
But, actually, all you really need to do in many cases is the AWS :stub_requests and it will achieve what you want.
This is how I got this working. First you have to have the fakeweb gem or it will fail. You also have to have an empty file in the spec/support/paperclip/[model]/[attachment_name][ext] path.
What I did was to copy the code from Paperclip and paste it into my factory. I was unable to get the 'paperclip_fixture' working.
factory :attachment do
file do |a|
# Stubbed Paperclip attachment from: https://github.com/thoughtbot/paperclip/blob/master/shoulda_macros/paperclip.rb#L68
# FIX: This was the only way I made this work. Calling the paperclip_fixture directly didn't work.
# See: http://stackoverflow.com/questions/4941586/stubbing-paperclip-s3-requests-in-specs
model, attachment, extension = "customer_attachment", "file", "doc"
definition = model.gsub(" ", "_").classify.constantize.
attachment_definitions[attachment.to_sym]
path = "http://s3.amazonaws.com/:id/#{definition[:path]}"
path.gsub!(/:([^\/\.]+)/) do |match|
"([^\/\.]+)"
end
begin
FakeWeb.register_uri(:put, Regexp.new(path), :body => "OK")
rescue NameError
raise NameError, "the stub_paperclip_s3 shoulda macro requires the fakeweb gem."
end
base_path = File.join(Rails.root, "spec", "support", "paperclip")
File.new(File.join(base_path, model, "#{attachment}.#{extension}"))
end
end
This is how I stub a file from paperclip without using the shoulda helpers.
before(:each) do
#sheet = double('sheet')
#sheet.stub(:url).and_return(File.join(Rails.root, 'spec','fixtures','files', 'file.xls'))
active_record_object.stub(:sheet).and_return(#sheet)
end
Hope this helps someone.
I just came across this upgrading an app from Rails 2.3 to Rails 3.0 after having followed Testing Paperclip on S3 with Cucumber & Factory Girl.
Instead of including the Paperclip::Shoulda module into the Factory class, I had to include it into the FactoryGirl::DefinitionProxy class, so I changed this:
class Factory
include Paperclip::Shoulda
end
to
class FactoryGirl::DefinitionProxy
include Paperclip::Shoulda
end
For reference, I'm using paperclip 2.4.1 and factory_girl 2.0.5.

How do I test a file upload in rails?

I have a controller which is responsible for accepting JSON files and then processing the JSON files to do some user maintenance for our application. In user testing the file upload and processing works, but of course I would like to automate the process of testing the user maintenance in our testing. How can I upload a file to a controller in the functional testing framework?
Searched for this question and could not find it, or its answer on Stack Overflow, but found it elsewhere, so I'm asking to make it available on SO.
The rails framework has a function fixture_file_upload (Rails 2 Rails 3, Rails 5), which will search your fixtures directory for the file specified and will make it available as a test file for the controller in functional testing. To use it:
1) Put your file to be uploaded in the test in your fixtures/files subdirectory for testing.
2) In your unit test you can get your testing file by calling fixture_file_upload('path','mime-type').
e.g.:
bulk_json = fixture_file_upload('files/bulk_bookmark.json','application/json')
3) call the post method to hit the controller action you want, passing the object returned by fixture_file_upload as the parameter for the upload.
e.g.:
post :bookmark, :bulkfile => bulk_json
Or in Rails 5: post :bookmark, params: {bulkfile: bulk_json}
This will run through the simulated post process using a Tempfile copy of the file in your fixtures directory and then return to your unit test so you can start examining the results of the post.
Mori's answer is correct, except that in Rails 3 instead of "ActionController::TestUploadedFile.new" you have to use "Rack::Test::UploadedFile.new".
The file object that is created can then be used as a parameter value in Rspec or TestUnit tests.
test "image upload" do
test_image = path-to-fixtures-image + "/Test.jpg"
file = Rack::Test::UploadedFile.new(test_image, "image/jpeg")
post "/create", :user => { :avatar => file }
# assert desired results
post "/create", :user => { :avatar => file }
assert_response 201
assert_response :success
end
I think it's better to use the new ActionDispatch::Http::UploadedFile this way:
uploaded_file = ActionDispatch::Http::UploadedFile.new({
:tempfile => File.new(Rails.root.join("test/fixtures/files/test.jpg"))
})
assert model.valid?
This way you can use the same methods you are using in your validations (as for example tempfile).
From The Rspec Book, B13.0:
Rails’ provides an ActionController::TestUploadedFile class which can be used to represent an uploaded file in the params hash of a controller spec, like this:
describe UsersController, "POST create" do
after do
# if files are stored on the file system
# be sure to clean them up
end
it "should be able to upload a user's avatar image" do
image = fixture_path + "/test_avatar.png"
file = ActionController::TestUploadedFile.new image, "image/png"
post :create, :user => { :avatar => file }
User.last.avatar.original_filename.should == "test_avatar.png"
end
end
This spec would require that you have a test_avatar.png image in the spec/fixtures directory. It would take that file, upload it to the controller,
and the controller would create and save a real User model.
You want to use fixtures_file_upload. You will put your test file in a subdirectory of the fixtures directory and then pass in the path to fixtures_file_upload. Here is an example of code, using fixture file upload
If you are using default rails test with factory girl. Fine below code.
factory :image_100_100 do
image File.new(File.join(::Rails.root.to_s, "/test/images", "100_100.jpg"))
end
Note: you will have to keep an dummy image in /test/images/100_100.jpg.
It works perfectly.
Cheers!
if you are getting the file in your controller with the following
json_file = params[:json_file]
FileUtils.mv(json_file.tempfile, File.expand_path('.')+'/tmp/newfile.json')
then try the following in your specs:
json_file = mock('JsonFile')
json_file.should_receive(:tempfile).and_return("files/bulk_bookmark.json")
post 'import', :json_file => json_file
response.should be_success
This will make the fake method to 'tempfile' method, which will return the path to the loaded file.

Resources