Back to Repositories

Testing Configuration and Storage Management in CarrierWave

This test suite examines the configuration functionality of CarrierWave, a popular file upload library for Ruby. It validates core configuration mechanisms, storage handling, and inheritance behavior of uploader classes. The tests ensure robust configuration management and proper storage backend integration.

Test Coverage Overview

The test suite provides comprehensive coverage of CarrierWave’s configuration system:

  • Configuration parameter management and inheritance
  • Storage backend selection and validation
  • Cache storage functionality
  • Dynamic configuration using proc objects

Implementation Analysis

The testing approach utilizes RSpec’s behavior-driven development patterns with extensive use of describe blocks and context-specific testing. It employs dynamic class creation and method manipulation to isolate test cases and prevent test pollution.

Key patterns include before/after hooks for setup/teardown, let blocks for lazy initialization, and careful handling of class-level configuration.

Technical Details

  • Testing Framework: RSpec
  • Mocking: RSpec doubles for storage backends
  • Setup: Dynamic class generation using Class.new
  • Configuration: Runtime method definition and removal
  • Test Isolation: Method undefining between tests

Best Practices Demonstrated

The test suite exemplifies several testing best practices:

  • Proper test isolation through method cleanup
  • Comprehensive edge case coverage
  • Clear test organization using nested describe blocks
  • Effective use of RSpec’s expectation syntax
  • Thorough validation of inheritance behavior

carrierwaveuploader/carrierwave

spec/uploader/configuration_spec.rb

            
require 'spec_helper'

describe CarrierWave do
  describe '.configure' do
    before do
      CarrierWave::Uploader::Base.add_config :test_config
      CarrierWave.configure { |config| config.test_config = "foo" }
    end
    after do
      CarrierWave::Uploader::Base.singleton_class.send :undef_method, :test_config
      CarrierWave::Uploader::Base.singleton_class.send :undef_method, :test_config=
      CarrierWave::Uploader::Base.send :undef_method, :test_config
      CarrierWave::Uploader::Base.send :undef_method, :test_config=
    end

    it "proxies to Uploader configuration" do
      expect(CarrierWave::Uploader::Base.test_config).to eq('foo')
    end
  end
end

describe CarrierWave::Uploader::Base do
  let(:uploader_class) { Class.new(CarrierWave::Uploader::Base) }

  describe '.configure' do
    before do
      uploader_class.tap do |uc|
        uc.add_config :foo_bar
        uc.configure { |config| config.foo_bar = "monkey" }
      end
    end
    after do
      uploader_class.singleton_class.send :undef_method, :foo_bar
      uploader_class.singleton_class.send :undef_method, :foo_bar=
      uploader_class.send :undef_method, :foo_bar
      uploader_class.send :undef_method, :foo_bar=
    end

    it "sets a configuration parameter" do
      expect(uploader_class.foo_bar).to eq('monkey')
    end
  end

  describe ".storage" do
    let(:storage) { double('some kind of storage').as_null_object }

    it "sets the storage if an argument is given" do
      uploader_class.storage(storage)

      expect(uploader_class.storage).to storage
    end

    it "defaults to file" do
      expect(uploader_class.storage).to eq(CarrierWave::Storage::File)
    end

    it "sets the storage from the configured shortcuts if a symbol is given" do
      uploader_class.storage :file
      expect(uploader_class.storage).to eq(CarrierWave::Storage::File)
    end

    context "when inherited" do
      before { uploader_class.storage(:fog) }
      let(:subclass) { Class.new(uploader_class) }

      it "remembers the storage" do
        expect(subclass.storage).to eq(CarrierWave::Storage::Fog)
      end

      it "'s changeable" do
        expect(subclass.storage).to eq(CarrierWave::Storage::Fog)

        subclass.storage(:file)
        expect(subclass.storage).to eq(CarrierWave::Storage::File)
      end
    end

    it "raises UnknownStorageError when set unknown storage" do
      expect{ uploader_class.storage :unknown }.to raise_error(CarrierWave::UnknownStorageError, "Unknown storage: unknown")
    end
  end

  describe ".cache_storage" do
    it "returns the same storage as given by #storage" do
      uploader_class.storage :file
      expect(uploader_class.new.send(:cache_storage)).to be_a(CarrierWave::Storage::File)
      uploader_class.storage :fog
      expect(uploader_class.new.send(:cache_storage)).to be_a(CarrierWave::Storage::Fog)
    end

    it "can be explicitly set" do
      uploader_class.storage :fog
      uploader_class.cache_storage :file
      expect(uploader_class.new.send(:cache_storage)).to be_a(CarrierWave::Storage::File)
    end
  end

  describe '.add_config' do
    before do
      uploader_class.add_config :foo_bar
      uploader_class.foo_bar = 'foo'
    end
    after do
      uploader_class.singleton_class.send :undef_method, :foo_bar
      uploader_class.singleton_class.send :undef_method, :foo_bar=
      uploader_class.send :undef_method, :foo_bar
      uploader_class.send :undef_method, :foo_bar=
    end

    it "adds a class level accessor" do
      expect(uploader_class.foo_bar).to eq('foo')
    end

    it "adds an instance level accessor" do
      expect(uploader_class.new.foo_bar).to eq('foo')
    end

    it "adds a convenient in-class setter" do
      expect(uploader_class.foo_bar).to eq('foo')
    end

    it "adds a convenient in-class setter which can assign 'false' as a value" do
      expect { uploader_class.foo_bar(false) }.to change { uploader_class.foo_bar }.from('foo').to(false)
    end

    ['foo', :foo, 45, ['foo', :bar]].each do |val|
      it "'s inheritable for a #{val.class}" do
        uploader_class.singleton_class.send :undef_method, :foo_bar
        uploader_class.singleton_class.send :undef_method, :foo_bar=
        uploader_class.send :undef_method, :foo_bar
        uploader_class.send :undef_method, :foo_bar=

        uploader_class.add_config :foo_bar
        child_class = Class.new(uploader_class)

        uploader_class.foo_bar = val
        expect(uploader_class.foo_bar).to eq(val)
        expect(child_class.foo_bar).to eq(val)

        child_class.foo_bar = "bar"
        expect(child_class.foo_bar).to eq("bar")

        expect(uploader_class.foo_bar).to eq(val)
      end
    end

    describe "assigning a proc to a config attribute" do
      before do
        uploader_class.tap do |uc|
          uc.add_config :hoobatz
          uc.hoobatz = this_proc
        end
      end

      after do
        uploader_class.singleton_class.send :undef_method, :hoobatz
        uploader_class.singleton_class.send :undef_method, :hoobatz=
        uploader_class.send :undef_method, :hoobatz
        uploader_class.send :undef_method, :hoobatz=
      end

      context "when the proc accepts no arguments" do
        let(:this_proc) { proc { "a return value" } }

        it "calls the proc without arguments" do
          expect(uploader_class.new.hoobatz).to eq("a return value")
        end
      end

      context "when the proc accepts one argument" do
        let(:this_proc) { proc { |arg1| expect(arg1).to be_an_instance_of(uploader_class) } }

        it "calls the proc with an instance of the uploader" do
          uploader_class.new.hoobatz
        end
      end
    end
  end
end