hirowatari
hirowatari

Reputation: 3302

How to upload a file in a Rails Rspec request spec

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

Answers (10)

Martin Sommer
Martin Sommer

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

Nick of Time
Nick of Time

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

Samuel
Samuel

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

user18633836
user18633836

Reputation:

 file = File.expand_path("path/to/fixture_file", __dir__)
 Rack::Test::UploadedFile.new(file, 'application/pdf')

Upvotes: 0

L.Youl
L.Youl

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

Argonus
Argonus

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
  1. You can use Uploaded file but you have to give full path
  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

Sergii Chub
Sergii Chub

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

wacha
wacha

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

vipin
vipin

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

Rajdeep Singh
Rajdeep Singh

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

Related Questions