Search code examples
ruby-on-railsfiletestingmockingrails-activestorage

How to stub file size on Active Storage test? (Test::Unit)


I'm using Active Storage in a personal project. I want to check if the max size of files is being validated. I don't wanna use a real file, but I don't know how to stub an object.

Here's the test code:

test "should not upload file bigger than max size allowed" do
  refute @page.file.attached?

  patch "/#{@page.url}", params: {
    page: {
      url: "/#{@page.url}",
      file: my_stub_file_with_big_size
    }
  }
  assert_response :not_acceptable
  @page.reload

  refute  @page.file.attached?
end

Here's the validation on model:

def file_size
  if file.attached? && file.byte_size > MAX_FILE_SIZE
    file.purge
    errors.add(:file, "File is too big. Max size is 20mb.")
  end
end

Solution

  • I read the code from your project, and I see that you've come up with an interesting solution, but here's another that matches your requirements.

    Step 1 - Add mocha

    Mocha is the de facto stubbing library for MiniTest fans, so add it to your Gemfile like so.

    group :test do
      gem 'mocha'
    end
    

    Then run bundle install and require mocha in your test helper like so:

    # test/test_helper.rb
    
    ENV['RAILS_ENV'] ||= 'test'
    
    require_relative '../config/environment'
    require 'rails/test_help'
    
    # add this line
    require 'mocha/minitest'
    
    class ActiveSupport::TestCase    
      # Setup all fixtures in test/fixtures/*.yml for all tests in alphabetical order.
      fixtures :all    
    end
    

    Step 2 - Create a dummy test file

    Place a small file named 'test.png' in test/fixtures/files/. Note that my file is around 20KB in size, it's pretty small.

    Step 3 - Add a unit test

    Add the following to test/models/page_test.rb:

    test 'file has max size' do
      # get the path of the file
      fixture_path = File.join(Rails.root, 'test', 'fixtures', 'files', 'test.png')
    
      # open the file  
      fixture_file = File.open(fixture_path)
    
      # stub the file size to 21 MB
      fixture_file.stubs(:size).returns(21.megabytes)
    
      # attach the file to your @page instance
      @page.file.attach(io: fixture_file, filename: 'test.png')
    
      # the record should now be invalid
      refute @page.valid?
    
      # it should not have an attachment
      refute @page.file.attached?
    
      # assert the error message matches a regex 
      assert_match /is too big/, @page.errors[:file].to_s
    end
    

    Step 4 - Test inside the maximum filesize boundary

    Let's make sure that a file of 20MB can be accepted by adding the following to test/models/page_test.rb:

    test 'file with valid size' do
      # get the path of the file
      fixture_path = File.join(Rails.root, 'test', 'fixtures', 'files', 'test.png')
    
      # open the file  
      fixture_file = File.open(fixture_path)
    
      # stub the file size to 20 MB
      fixture_file.stubs(:size).returns(20.megabytes)
    
      # attach the file to your @page instance
      @page.file.attach(io: fixture_file, filename: 'test.png')
    
      # the record should now be valid
      assert @page.valid?
    
      # it should have an attachment
      assert @page.file.attached?
    
      # probably unnecessary, but let's test that there are no errors on @page
      assert_empty @page.errors[:file]
    end
    

    Step 5 - Add a controller test for the happy path

    Let's add a controller test for the happy path:

    test 'should update page' do
      # fixture_file_upload knows where the fixtures folder is
      # and makes sure we have a correctly signed file
      fixture_file = fixture_file_upload('files/test.png', 'image/png')
    
      # we need to stub the byte_size of any instance of ActiveStorage::Blob
      # as that is what our validations are going to be run against 
      ActiveStorage::Blob.any_instance.stubs(:byte_size).returns(20.megabytes)
    
      # make sure our ActiveStorage records are created
      assert_difference ['ActiveStorage::Blob.count', 'ActiveStorage::Attachment.count'] do
        # add our fixture file into the params
        patch page_url, params: { page: { file: fixture_file } }
      end
    
      # make sure we are redirected to the right place 
      assert_redirected_to page_url
    end
    

    Step 6 - Add a controller test for the sad path

    Time to test upload failure:

    test 'should render edit template when update fails' do
      # like before, we get correctly formatted test file by calling
      # fixture_file_upload method and passing in the test file
      fixture_file = fixture_file_upload('files/test.png', 'image/png')
    
      # stub the byte_size to be 21MB
      ActiveStorage::Blob.any_instance.stubs(:byte_size).returns(21.megabytes)
    
      # make sure zero ActiveStorage records are created
      assert_no_difference ['ActiveStorage::Blob.count', 'ActiveStorage::Attachment.count'] do
        # send the request
        patch page_url, params: { page: { file: fixture_file } }
      end
    
      # check the response body for an error message
      assert_match /is too big/, response.body.to_s
    end
    

    That's it. Hope it helps.