ruby-on-railsrspecrails-apirails-activestorage

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.


Solution

  • 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 }