—
title: Testing with Shrine
¶ ↑
import Tabs from ‘@theme/Tabs’; import TabItem from ‘@theme/TabItem’;
The goal of this guide is to provide some useful tips for testing file attachments implemented with Shrine
in your application.
Callbacks¶ ↑
When you first try to test file attachments, you might experience that files are not being promoted to permanent storage. This is because your tests are likely setup to be wrapped inside database transactions, and that doesn’t work with Shrine
callbacks.
Specifically, Shrine
uses “after commit” callbacks for promoting and deleting attached files. This means that if your tests are wrapped inside transactions, those Shrine
actions will happen only after those transactions commit, which happens only after the test has already finished.
# Promoting will happen only after the test transaction commits it "can attach images" do photo = Photo.create(image: file) photo.image.storage_key #=> :cache (we expected it to be promoted to permanent storage) end
For file attachments to properly work, you’ll need to disable transactions for those tests. For Rails apps you can tell Rails not to use transactions, and instead use libraries like DatabaseCleaner which allow you to use table truncation or deletion strategies instead of transactions.
RSpec.configure do |config| config.use_transactional_fixtures = false end
Storage¶ ↑
If you’re using FileSystem storage and your tests run in a single process, you can switch to Shrine::Storage::Memory
, which is both faster and doesn’t require you to clean up anything between tests.
require "shrine/storage/memory" Shrine.storages = { cache: Shrine::Storage::Memory.new, store: Shrine::Storage::Memory.new, }
If you’re using AWS S3 storage, you can use MinIO (explained below) instead of S3, both in test and development environment. Alternatively, you can stub aws-sdk-s3 requests in tests.
MinIO¶ ↑
MinIO is an open source object storage server with AWS S3 compatible API which you can run locally. The advantage of using MinIO for your development and test environments is that all AWS S3 functionality should still continue to work, including direct uploads, so you don’t need to update your code.
If you’re on a Mac you can install it with Homebrew:
$ brew install minio/stable/minio
Afterwards you can start the MinIO server and give it a directory where it will store the data:
$ minio server data/
This command will print out the credentials for the running MinIO server, as well as a link to the MinIO web interface. Follow that link and create a new bucket. Once you’ve done that, you can configure Shrine::Storage::S3
to use your MinIO server:
Shrine::Storage::S3.new( access_key_id: "<MINIO_ACCESS_KEY>", # "AccessKey" value secret_access_key: "<MINIO_SECRET_KEY>", # "SecretKey" value endpoint: "<MINIO_ENDPOINT>", # "Endpoint" value bucket: "<MINIO_BUCKET>", # name of the bucket you created region: "us-east-1", force_path_style: true, )
The :endpoint
option will make aws-sdk-s3 point all URLs to your MinIO server (instead of s3.amazonaws.com
), and :force_path_style
tells it not to use subdomains when generating URLs.
Test data¶ ↑
We want to keep our tests fast, so when we’re setting up files for tests, we want to avoid expensive operations such as file processing and metadata extraction.
We can create a helper method that will create attached file data for us, and use that with our factories/fixtures.
module TestData module_function def image_data attacher = Shrine::Attacher.new attacher.set(uploaded_image) # if you're processing derivatives attacher.set_derivatives( large: uploaded_image, medium: uploaded_image, small: uploaded_image, ) attacher.column_data # or attacher.data in case of postgres jsonb column end def uploaded_image file = File.open("test/files/image.jpg", binmode: true) # for performance we skip metadata extraction and assign test metadata uploaded_file = Shrine.upload(file, :store, metadata: false) uploaded_file.metadata.merge!( "size" => File.size(file.path), "mime_type" => "image/jpeg", "filename" => "test.jpg", ) uploaded_file end end
<Tabs> <TabItem value=“factory_bot” label=“FactoryBot”>
factory :photo do image_data { TestData.image_data } end
</TabItem> <TabItem value=“fixtures” label=“Rails YAML fixtures”>
photo: image_data: <%= TestData.image_data %>
</TabItem> </Tabs>
Unit tests¶ ↑
For testing attachment in your unit tests, you can assign plain File
objects:
RSpec.describe ImageUploader do let(:image) { photo.image } let(:derivatives) { photo.image_derivatives } let(:photo) { Photo.create(image: File.open("test/files/image.png", "rb")) } it "extracts metadata" do expect(image.mime_type).to eq("image/png") expect(image.extension).to eq("png") expect(image.size).to be_instance_of(Integer) expect(image.width).to be_instance_of(Integer) expect(image.height).to be_instance_of(Integer) end it "generates derivatives" do expect(derivatives[:small]).to be_kind_of(Shrine::UploadedFile) expect(derivatives[:medium]).to be_kind_of(Shrine::UploadedFile) expect(derivatives[:large]).to be_kind_of(Shrine::UploadedFile) end end
Acceptance tests¶ ↑
In acceptance tests you’re testing your app end-to-end, and you likely want to also test file attachments here. Here are examples for some common use cases:
<Tabs> <TabItem value=“capybara” label=“Capybara”>
attach_file("#image-field", "test/files/image.jpg")
</TabItem> <TabItem value=“rack-test” label=“rack-test”>
post "/photos", photo: { image: Rack::Test::UploadedFile.new("test/files/image.jpg", "image/jpeg") }
</TabItem> </Tabs>
If you want to test requests with cached attachment data, you can do so as follows:
cached_file = Shrine.upload(file, :cache) post "/photos", photo: { image: cached_file.to_json }
Background jobs¶ ↑
If you’re using background jobs with Shrine
, you probably want to make them synchronous in tests. See your backgrounding library docs for how to make jobs synchronous.
<Tabs> <TabItem value=“activejob” label=“Active Job”>
ActiveJob::Base.queue_adapter = :inline
</TabItem> <TabItem value=“sidekiq” label=“Sidekiq”>
require "sidekiq/testing" Sidekiq::Testing.inline!
</TabItem> <TabItem value=“sucker_punch” label=“SuckerPunch”>
require "sucker_punch/testing/inline"
</TabItem> </Tabs>
Processing¶ ↑
If you’re testing your attachment flow which includes processing derivatives, you might want to disable the processing for certain tests. You can do this by temporarily overriding the processor:
module TestMode module_function def disable_processing(attacher, processor_name = :default) attacher.class.instance_exec do original_processor = derivatives_processor derivatives_processor(processor_name) { Hash.new } yield derivatives_processor(processor_name, &original_processor) end end end
TestMode.disable_processing(Photo.image_attacher) do photo = Photo.new photo.file = File.open("test/files/image.png", "rb") photo.save end
Testing direct upload¶ ↑
If you’d like to unit-test direct upload on the server side, you can emulate it by uploading a file to cache
and then assigning it to the record.
cached_file = Shrine.upload(some_file, :cache) record.attachment = cached_file.to_json