How to add Active Storage attachement as a Factory Bot (or Factory Girl) trait.

technology used in the example: Rails 5.2.0, Ruby 2.5, Factory Bot 4.10, RSpec 3.7

# app/models/account.rb class Account < ActiveRecord::Base has_attached :image end # spec/rails_helper.rb FactoryBot::SyntaxRunner.class_eval do include ActionDispatch::TestProcess end # spec/factories/accounts.rb FactoryBot.define do factory :account do name 'Tomas' trait :with_avatar do avatar { fixture_file_upload(Rails.root.join('spec', 'support', 'assets', 'test-image.png'), 'image/png') } end end end

make sure you have test image to spec/support/assets/test-image.png

Now you can do:

# spec/models/account_spec.rb let(:account) { build :account, :with_avatar) let(:account) { build_stubbed :account, :with_avatar) let(:account) { create :account, :with_avatar)

Alternative way:

# spec/factories/accounts.rb FactoryBot.define do factory :account do name 'Tomas' trait :with_avatar do after :create do |account| file_path = Rails.root.join('spec', 'support', 'assets', 'test-image.png') file = fixture_file_upload(file_path, 'image/png') account.avatar.attach(file) end end end end # spec/models/account_spec.rb let(:account) { create :account, :with_avatar)

didn’t try it but teoretically you can use this approach to attach has_many_attached

Using Test Helpers

My favorit approach is to create helper module that would extend everything required for attaching images:

# spec/support/files_test_helper.rb module FilesTestHelper extend self extend ActionDispatch::TestProcess def png_name; 'test-image.png' end def png; upload(png_name, 'image/png') end def jpg_name; 'test-image.jpg' end def jpg; upload(jpg_name, 'image/jpg') end def tiff_name; 'test-image.tiff' end def tiff; upload(tiff_name, 'image/tiff') end def pdf_name; 'test.pdf' end def pdf; upload(pdf_name, 'application/pdf') end private def upload(name, type) file_path = Rails.root.join('spec', 'support', 'assets', name) fixture_file_upload(file_path, type) end end

# spec/factories/accounts.rb FactoryBot.define do factory :account do name 'Tomas' trait :with_avatar do avatar { FilesTestHelper.png } end end end

This way I don’t have to polute Factory Bot with: FactoryBot::SyntaxRunner.class_eval { include ActionDispatch::TestProcess } making the debugging easier for junior developers.

And I’m also able to reuse the test helper e.g. in controler specs when testing upload:

RSpec.describe V3::AccountsController, type: :controller do describe 'POST create' do let(:avatar) { FilesTestHelper.png } def trigger do post :create, params: { avatar: avatar, name: 'Zdenka' } end it 'should upload the file' do expect { trigger }.to change{ ActiveStorage::Attachment.count }.by(1) end it 'should create the account' do expect { trigger }.to change{ Account.count }.by(1) account = Account.last expect(account.avatar).to be_attached expect(account.avatar.filename).to eq FilesTestHelper.png_name expect(account.name).to eq 'Zdenka' end end end

Related articles:

Discussion: