Rspec : stubbing ActiveStorage download method - ruby-on-rails

I work on a system that stores cached data on S3 with ActiveStorage before using it for something else. In my spec, I want to stub the download method of this file, and load a specific file for testing purpose.
allow(user.cached_data).to receive(:download)
.and_return(read_json_file('sample_data.json'))
(read_json_file is a spec helper that File.read then JSON.parse a data file.)
I get this error :
#<ActiveStorage::Attached::One:0x00007f9304a934d8 #name="cached_data",
#record=#<User id: 4, name: "Bob", email: "bob#email.com",
created_at: "2019-08-22 09:11:16", updated_at: "2019-08-22 09:11:16">,
#dependent=:purge_later> does not implement: download
I don't get it, the docs clearly say that this object is supposed to implement download.
Edit
As suggested by Jignesh and Stephen, I tried this :
allow(user.cached_data.blob).to receive(:download)
.and_return(read_json_file('sample_data.json'))
and I got the following error :
Module::DelegationError:
blob delegated to attachment, but attachment is nil
user is generated by FactoryBot, so I'm currently trying to attach my cached_data sample file to that object.
My factory looks like that :
FactoryBot.define do
factory :user
name { 'Robert' }
email { 'robert#email.com' }
after(:build) do |user|
user.cached_data.attach(io: File.open("spec/support/sample_data.json"), filename: 'sample.json', content_type: 'application/json')
end
end
end
But when I add that after build block to the factory, I get the following error :
ActiveRecord::LockWaitTimeout:
Mysql2::Error::TimeoutError: Lock wait timeout exceeded
Maybe it's another Stackoverflow question.

As other have pointed out in the comments, #download is implemented on the ActiveStorage::Blob class, not ActiveStorage::Attached::One. You can download the file with the following:
if user.cached_data.attached?
user.cached_data.blob.download
end
I added the check to ensure cached_data is attached because blob delegates to the attachment and would fail if not attached.
Here is the documentation for #download.

Related

How to Simulate ActiveStorage Uploads in RSpec Request Specs

I'm writing an API-Only Rails App (rails 5.2) and I need to make use of ActiveStorage. I want to write an RSpec Request Spec to ensure that file uploads work properly, but that's proving very difficult. Here's my spec so far:
RSpec.describe 'POST /profile/photos', type: :request do
let(:url) { '/profile/photos' }
let(:file) { fixture_file_upload('photo.jpg') }
before do
log_user_in
## Do a file upload ##
post '/rails/active_storage/direct_uploads', params: {
blob: {
filename: "woman.jpg",
content_type: "image/jpeg",
byte_size: File.size(file.path),
checksum: Digest::MD5.base64digest(File.read(file.path))
}
}
post url, params: {signed_id: json["signed_id"]}
end
it "Performs an upload" do
# Never gets here, error instead
end
end
I've tried using the put helper to upload the file between the first and second post call in the before step, but I keep running into 422 unprocessable entity errors, likely because the put helper doesn't support setting the raw request body. But I'm not entirely sure what the format of that put should be, or if there's a better way to test this.
I've tried using fixture_file_upload, as described in this question:
put json["direct_upload"]["url"], params: {file: fixture_file_upload('photo.jpg')}
But that request returns 422 like all of my other attempts. I think the direct_upload URL really wants the body of the request to contain the file and nothing else.
I suspect there's a lot wrong with my approach here, but the Rails docs are somewhat sparse on how to use ActiveStorage if you're not using the out-of-the-box javascript to hide most of the interactions.
You probably don't care about testing the Active Storage engine so you don't need to post '/rails/active_storage/direct_uploads', you just need a valid signature.
I ended up creating an ActiveStorage::Blob by hand and then I can ask it for the signature. Something like this off in a helper:
def blob_for(name)
ActiveStorage::Blob.create_and_upload!(
io: File.open(Rails.root.join(file_fixture(name)), 'rb'),
filename: name,
content_type: 'image/jpeg' # Or figure it out from `name` if you have non-JPEGs
)
end
and then in your specs you can say:
post url, params: { signed_id: blob_for('photo.jpg').signed_id }

Testing if a file uploads sucessfully in Rails

I'm running Rails 3.2.13.
In one of my controllers, the application processes a form by which the users sends a file.
The application analyses the file and saves it OR NOT, depending on the analysis.
I want to write a functional test which tests that the correct file gets saved, and that the wrong file doesn't get saved.
I'm loading the file from test/fixtures/files, like this:
resource_file = Rack::Test::UploadedFile.new(Rails.root.
join('test', 'fixtures', 'files', 'f1.jpg'))
When running the test, where does this file gets saved?
/spec/support/fixture_file.rb
include ActionDispatch::TestProcess
def fixture_file(filename, extension)
fixture_file_upload(Rails.root.join('spec', 'fixtures', filename), extension)
end
/spec/factories/offers.rb
FactoryGirl.define do
factory :offer do
large_picture { fixture_file("ostrov.jpg", "image/jpeg") }
end
end
P.S It also works for TestUnit

Rails - Functional test failing because of validation

I can't for the life of me figure out what's going wrong here. This is the situation:
I have a document model using paperclip for pdf attachments
The functional test uploads a document and fails
The reason for this is my validation that it is a pdf file - somehow that validation fails
However, the file is a pdf and the validation should not fail
The validation only fails in the test - doing it manually by uploading the file it complains about works absolutely fine
Here is my failing test (the count is not increased by one):
test "should create document" do
assert_difference('Document.count') do
post :create, document: { pdf: fixture_file_upload("../files/document_test_file.pdf"), language: #document.language, published_on: #document.published_on, tags: #document.tags, title: #document.title, user_id: #user }
end
assert_redirected_to document_path(assigns(:document))
end
This is my validation in the document model:
def document_is_a_pdf
if !self.pdf.content_type.match(/pdf/)
errors.add(:pdf, "must be a pdf file")
false
end
end
If I do not call that validation in the model, the test runs fine. What am I doing wrong here?
I know this is an old question but if anyone still needs help,
From http://apidock.com/rails/ActionController/TestProcess/fixture_file_upload
fixture_file_upload(path, mime_type = nil, binary = false)
Fixture file upload method by default will set the mime type as nil so simply changing the mime type as below will correct this
fixture_file_upload("../files/document_test_file.pdf", 'application/pdf')
I found the problem. Somehow, while testing, the content type could not be determined. That is why the validation of the content type failed and the test did not pass.
I added the content type to the accessible attributes inside my document model and inserted the content type in the test (second attribute inside the document hash):
test "should create document" do
assert_difference('Document.count') do
post :create, document: { pdf: fixture_file_upload("../files/document_test_file.pdf"), pdf_content_type: "application/pdf", language: #document.language, published_on: #document.published_on, tags: #document.tags, title: #document.title, user_id: #user }
end
assert_redirected_to document_path(assigns(:document))
end

Unit testing paperclip uploads with Rspec (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.

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