46

I have just switched to using ActiveStorage on rails 5.1.4 and I am new to TDD and struggling to figure out how to test a model that has_one_attached :avatar

require 'rails_helper'

RSpec.describe User, :type => :model do

  let (:valid_user) { FactoryBot.build(:user) }
  describe "Upload avatar" do
    context "with a valid image" do      
      it "saves the image" do
        valid_user.save!        
        saved_file = valid_user.avatar.attach(io: File.open("/home/ubuntu/workspace/spec/fixtures/files/avatar.jpg"), filename: "face.jpg", content_type: "image/jpg")
        expect(saved_file).to be_an_instance_of(ActiveStorage::Attachment::One)
      end
    end
  end 

end

But I am getting the following error:

Failures:

  1) User Upload avatar with a valid image saves the image
     Failure/Error:
       saved_file = valid_user.avatar.attach(io: File.open("/home/ubuntu/workspace/spec/fixtures/files/avatar.jpg"), filename: "face.jpg", 
                                             content_type: "image/jpg")


 NoMethodError:
   undefined method `upload' for nil:NilClass
   Did you mean?  load
 # /usr/local/rvm/gems/ruby-2.3.4/gems/activestorage-0.1/lib/active_storage/blob.rb:48:in `upload'
 # /usr/local/rvm/gems/ruby-2.3.4/gems/activestorage-0.1/lib/active_storage/blob.rb:21:in `block in build_after_upload'
 # /usr/local/rvm/gems/ruby-2.3.4/gems/activestorage-0.1/lib/active_storage/blob.rb:16:in `tap'
 # /usr/local/rvm/gems/ruby-2.3.4/gems/activestorage-0.1/lib/active_storage/blob.rb:16:in `build_after_upload'
 # /usr/local/rvm/gems/ruby-2.3.4/gems/activestorage-0.1/lib/active_storage/blob.rb:26:in `create_after_upload!'
 # /usr/local/rvm/gems/ruby-2.3.4/gems/activestorage-0.1/lib/active_storage/attached.rb:25:in `create_blob_from'
 # /usr/local/rvm/gems/ruby-2.3.4/gems/activestorage-0.1/lib/active_storage/attached/one.rb:9:in `attach'
 # ./spec/models/user_spec.rb:47:in `block (4 levels) in <top (required)>'

Any hints?

Donato Azevedo
  • 1,378
  • 1
  • 13
  • 22

9 Answers9

34

I solved using

FactoryBot.define do
  factory :culture do
    name 'Soy'
    after(:build) do |culture|
      culture.image.attach(io: File.open(Rails.root.join('spec', 'factories', 'images', 'soy.jpeg')), filename: 'soy.jpeg', content_type: 'image/jpeg')
    end
  end
end

After

describe '#image' do
  subject { create(:culture).image }

  it { is_expected.to be_an_instance_of(ActiveStorage::Attached::One) }
end
vgsantoniazzi
  • 489
  • 4
  • 6
  • 2
    This gives you a possible false positive, since image will always be an instance of ActiveStorage::Attached::One, even when it is nil. Try the following in console: `User.new.avatar`. Notice that it is an instance of ActiveStorage::Attached::One. – stephenmurdoch Oct 07 '18 at 23:01
  • 1
    @stephenmurdoch: but what is the solution then? – Piezo Pea May 31 '22 at 08:25
20

Problem solved. After tracing the error to the ActiveStorage::Blob::upload method, where it said: Uploads the io to the service on the key for this blob. I realized I had not set the active_storage.service for the Test environment. Simply put, just add:

config.active_storage.service = :test

To config/environments/test.rb file

Donato Azevedo
  • 1,378
  • 1
  • 13
  • 22
12

Here is how I solved it

# model
class Report < ApplicationRecord
  has_one_attached :file
end
# factory
FactoryBot.define do
  factory :report, class: Report do 
    any_extra_field { 'its value' }
  end
end
# spec
require 'rails_helper'
RSpec.describe Report, type: :model do
  context "with a valid file" do
    before(:each) do
      @report = create(:report)
    end

    it "is attached" do
      @report.file.attach(
        io: File.open(Rails.root.join('spec', 'fixtures', 'file_name.xlsx')),
        filename: 'filename.xlsx',
        content_type: 'application/xlsx'
      )
      expect(@report.file).to be_attached
    end
  end
end

I hope it help you

axlfire1
  • 191
  • 2
  • 4
5

With some help of these post I did an update of rspec test for active storage at rails 6.1

# .\spec\models\activestorage_spec.rb
require 'rails_helper'

RSpec.describe User, :type => :model do
  before(:each) do
    @user = FactoryBot.create(:user)
        @user.save!
        saved_file = @user.pictures.attach(io: File.open("./storage/test.jpg"), filename: "test.jpg", content_type: "image/jpg")
  end

  describe "Upload picture" do
    context "with a valid picture" do    

      it "saves the picture" do        
        expect(@user.pictures).to be_attached
      end

    end
  end
end

Now you can only add some more tests

Mikołaj Wittbrodt
  • 399
  • 1
  • 4
  • 18
3

In config/environments/test.rb

config.active_storage.service = :test

In your spec

it {expect(valid_user.avatar).to be_attached}

rexmadden
  • 266
  • 4
  • 7
3

I was running into a similar nil error (Module::DelegationError: to_model delegated to attachment, but attachment is nil), but it was only occurring when multiple tests were using the same factory.

The problem appears to be that the cleanup from the previous test's teardown was deleting the file after it was attached on the next factory call. The key was updating the ActiveJob queue adapter to inline so that the file would be deleted right away.

Add both of these settings to config/environments/test.rb:

# Use inline job processing to make things happen immediately
config.active_job.queue_adapter = :inline

# Store uploaded files on the local file system in a temporary directory.
config.active_storage.service = :test
joe
  • 192
  • 1
  • 10
1

Why not:


RSpec.describe User, type: :model do
  describe 'relations' do
    it { is_expected.to have_one(:avatar_attachment) }
  end
end
brcebn
  • 1,571
  • 1
  • 23
  • 46
1

In my case, I have used rails shoulda gem

it { should have_one_attached(:image) }
Ryan M
  • 18,333
  • 31
  • 67
  • 74
-1
Rspec.describe User, type: :model do
  describe 'relations' do
    it { is_expected.to have_one_attached(:avatar_attachment) }
  end
end
  • 1
    Your answer could be improved by adding more information on what the code does and how it helps the OP. – Tyler2P Jun 09 '22 at 19:43