Reputation: 3302
I want to test uploading a file in a Rails Rspec request test. I'm using Paperclip for asset storage.
I've tried:
path = 'path/to/fixture_file'
params = { file: Rack::Test::UploadedFile.new(path, 'application/pdf', true) }
post v1_product_documents_path, params: params
In the controller, I get a string
"#Rack::Test::UploadedFile:0x0055b544479128>"
instead of the actual file.
The same code works in controller tests
Upvotes: 25
Views: 21319
Reputation: 566
My solution:
let(:action) do
post path,
headers: v4_headers.merge('Content-Type' => 'multipart/form-data', 'Accept' => 'application/json'),
params: { file: }, as: :multipart_form
end
Upvotes: 0
Reputation: 21
I ran into this issue while writing request specs for a POST endpoint that processes incoming email payloads that include file attachments.
this is what the request looked like in the request spec:
post api_v1_email_processor_path, params: params, as: :json
where params[:attachment]
was:
fixture_file_upload("spec/support/fixtures/files/report.csv", 'text/csv',)
and when I added some breakpoints in the controller, this is what the attachment was being converted into:
{"original_filename"=>"report.csv", "tempfile"=>"#<File:0x0000000121d2bfc8>", "content_type"=>"text/csv"}
as you can see, the "tempfile"
was being cast to a string value of "<File:0x0000000121d2bfc8>"
when I emulated the same request in a controller spec, to my huge confusion, there was no casting, and the breakpoint showed the param (correctly) as:
#<ActionDispatch::Http::UploadedFile:0x000000012d559488 @tempfile=#<Tempfile:/var/folders/jg/n_0rknb97kvddb0jgh6r3_x40000gn/T/RackMultipart20221008-45122-mzyqg5.csv>, @original_filename="report.csv", @content_type="text/csv", @headers="Content-Disposition: form-data; name=\"attachment1\"; filename=\"report.csv\"\r\nContent-Type: text/csv\r\nContent-Length: 24931\r\n">]
after seeing L.Youl's answer https://stackoverflow.com/a/67987482/14926068, it got me thinking that the issue was with how the request payload was being formatted, prior to being sent to the server through the Rack middleware. and so I tried taking off the as: :json
:
post api_v1_email_processor_path, params: params
and bingo, I got the same result as with the controller spec.
after inspecting response.request.content_type
in my request spec, I found that when I took the as: :json
off, its value changed from "application/json"
to
"multipart/form-data; boundary=----------XnJLe9ZIbbGUYtzPQJ16u1"
for completeness, I checked response.request.content_type
in the controller spec as well, and found that even with as: :json
in the request, the content_type
was being set to "multipart/form-data"
, and that accounted for the discrepancy in behavior between controller and request specs - it seems that as: :json
only affects the "content-type"
in the request specs.
in retrospect, it was obvious - json cannot be used to send actual files. but it took me a long time to figure it out because the equivalent controller spec was working fine and this was literally the only endpoint in my Rails api-only app that expected non-json payloads
Upvotes: 2
Reputation: 471
If you need the content type
in your RSpec test, it seems you can pass it as an argument to fixture_file_upload
(no idea why this method does not derive the content type from the OS).
For instance:
fixture_file_upload("pdfs/some.pdf", "application/pdf")
Upvotes: 0
Reputation:
file = File.expand_path("path/to/fixture_file", __dir__)
Rack::Test::UploadedFile.new(file, 'application/pdf')
Upvotes: 0
Reputation: 382
For others still getting something like "#Rack::Test::UploadedFile:0x0055b544479128>"
in the controller after implementing the approved solution above, I resolved this by changing my content-type
header to be 'application/x-www-form-urlencoded'
so that form data would be accepted.
Upvotes: 0
Reputation: 1035
In rails_helper.rb do
include ActionDispatch::TestProcess
include Rack::Test::Methods
Then you have few options. 1. You can use fixture_file_upload helper
let(:file) { fixture_file_upload('files/image.jpg') }
it 'it should work' do
post some_path, params: { uploads: { file: file } }
end
let(:file) { Rack::Test::UploadedFile.new(Rails.root.join('spec',
'fixtures', 'blank.jpg'), 'image/jpg') }
let(:params) { { attachment: file, variant_ids: ['', product.master.id] } }
let(:action) { post :create, product_id: product.slug, image: params }
Upvotes: 13
Reputation: 137
For rails 6 no need to do includes, just fixture_file_upload
. If you are using spec/fixtures/files
folder for fixtures you can use file_fixture
helper
let(:csv_file) { fixture_file_upload(file_fixture('file_example.csv')) }
subject(:http_request) { post upload_file_path, params: { file: csv_file } }
Upvotes: 8
Reputation: 499
Might be useful for other users: I got this problem because I mistakenly used a get
instead of a post
request in my specs.
Upvotes: 1
Reputation: 2510
try to use fixture_file_upload: fixture_file_upload
or if you wanted to use this in a factory
Rack::Test::UploadedFile.new(File.open(File.join(Rails.root, '/spec/fixtures/images/bob-weir.jpg')))
Upvotes: 23
Reputation: 17834
Under describe
block, include these modules
include Rack::Test::Methods
include ActionDispatch::TestProcess
Now try running the specs
path = 'path/to/fixture_file'
params = { "file" => Rack::Test::UploadedFile.new(path, 'application/pdf', true) }
post v1_product_documents_path, params: params
Also, I guess you forgot to add ,
between v1_product_documents_path
and params: params
, please add that and let me know.
Hope that helps!
Upvotes: 5