BrunoF
BrunoF

Reputation: 3523

Rails: Testing file upload validation (Shrine gem) at model spec

The following code tests image validation within a model spec in a Rails 4.2 app with RSpec 3.5 and the Shrine gem for file uploads.

My questions are:

Other aspects of the file upload setup are tested in controller and feature specs, which are irrelevant to this question.

RSpec.describe ShareImage, :type => :model do
  describe "#image", :focus do
    let(:image_file) do
      # Could not get fixture_file_upload to work, but that's irrelevant
      Rack::Test::UploadedFile.new(File.join(
        ActionController::TestCase.fixture_path, 'files', filename))
    end
    let(:share_image) { FactoryGirl.build(:share_image, image: image_file) }
    before(:each) { share_image.valid? }

    context "with a valid image file" do
      let(:filename) { 'image-valid.jpg' }
      it "attaches the image to this record" do
        expect(share_image.image.metadata["filename"]).to eq filename
      end
    end

    context "with JPG extension and 'text/plain' media type" do
      let(:filename) { 'image-with-text-media-type.jpg' }
      it "is invalid" do
        expect(share_image.errors[:image].to_s).to include("invalid file type")
      end
    end

    # TODO: Refactor the following test (it takes ~50 seconds to run)
    context "with a >10mb image file" do
      let(:filename) { 'image-11mb.jpg' }
      it "is invalid" do
        expect(share_image.errors[:image].to_s).to include("too large")
      end
    end
  end
end

Upvotes: 2

Views: 3055

Answers (2)

Janko
Janko

Reputation: 9305

I would recommend that you test metadata extraction and validation separately. The metadata extraction you need to test with real IOs, but for validation tests you can assign a cached file with the desired metadata which doesn't have to actually exist.

RSpec.describe ImageUploader do
  def uploaded_file(metadata = {})
    Shrine.uploaded_file(
      "id"       => "123",
      "storage"  => "cache",
      "metadata" => {"mime_type" => "image/jpeg", "size" => 100}.merge(metadata)
    )
  end

  let(:share_image) do
    FactoryGirl.build(:share_image, image: uploaded_file(metadata).to_json)
  end

  let(:metadata) { Hash.new }

  describe "validations" do
    before(:each) { share_image.valid? }

    context "when image is correct" do
      it "passes" do
        expect(share_image.errors).to be_empty
      end
    end

    context "when extension is correct but MIME types isn't" do
      let(:metadata) { Hash["filename" => "image.jpg", mime_type => "text/plain"] }

      it "fails" do
        expect(share_image.errors[:image].to_s).to include("isn't of allowed type")
      end
    end

    context "when file is larger than 10MB" do
      let(:metadata) { Hash["size" => 11 * 1024 * 1024] }

      it "fails" do
        expect(share_image.errors[:image].to_s).to include("too large")
      end
    end
  end
end

Upvotes: 5

Puhlze
Puhlze

Reputation: 2614

Rather than routing uploads through Rack::Test::UploadedFile, create the attachment meta-data record directly using a fixture or factory or directly in the test. The end result should be that you have the attachment meta data (which is what is constructed when you upload a file) that references your file without having to run it through the upload code. I'm not sure about the specifics of doing this with Shrine, but this technique works well with libraries like Paperclip. In Shrine it looks like this will mean constructing a Shrine::UploadedFile record directly that references your file.

Upvotes: 1

Related Questions