Back to Repositories

Testing URL Generation and Serialization Implementation in CarrierWave

This test suite validates the URL generation, JSON/XML serialization, and string representation functionality of CarrierWave uploaders. It covers core uploader behaviors for file URLs, version handling, and asset host configurations.

Test Coverage Overview

The test suite provides comprehensive coverage of URL-related functionality in CarrierWave uploaders. Key areas include:

  • Basic URL generation for cached files
  • Version-specific URL handling
  • Asset host configuration and URL prefixing
  • JSON and XML serialization of uploader instances
  • String representation of uploaders

Implementation Analysis

The testing approach uses RSpec’s describe/context blocks to organize related test cases hierarchically. It leverages let statements for setup and before/after hooks for test isolation. The implementation makes extensive use of RSpec’s expectation syntax and subject blocks for cleaner test definitions.

Technical Details

  • Testing Framework: RSpec
  • Support Libraries: ActiveSupport (JSON handling)
  • Setup: Uses temporary file fixtures and cache directories
  • Mocking: Employs RSpec’s mocking functionality for File and URL behavior
  • Test Isolation: Implements cleanup in after blocks

Best Practices Demonstrated

The test suite exemplifies several testing best practices:

  • Proper test isolation through before/after hooks
  • DRY test setup using let statements
  • Comprehensive edge case coverage
  • Clear test organization using nested contexts
  • Effective use of shared examples and subject blocks

carrierwaveuploader/carrierwave

spec/uploader/url_spec.rb

            
require 'spec_helper'
require 'active_support/json'

describe CarrierWave::Uploader do

  let(:uploader) { MyCoolUploader.new }

  before { class MyCoolUploader < CarrierWave::Uploader::Base; end } # rubocop:disable Lint/ConstantDefinitionInBlock

  after do
    FileUtils.rm_rf(public_path)
    Object.send(:remove_const, "MyCoolUploader") if defined?(::MyCoolUploader)
  end

  let(:cache_id) { '1369894322-345-1234-2255' }
  let(:test_file) { File.open(file_path(test_file_name)) }
  let(:test_file_name) { 'test.jpg' }

  before { allow(CarrierWave).to receive(:generate_cache_id).and_return(cache_id) }

  describe '#url' do
    subject(:url) { uploader.url }

    it { is_expected.to be_nil }

    it "doesn't raise exception when hash specified as argument" do
      expect { uploader.url({}) }.not_to raise_error
    end

    it "encodes the path of a file without an asset host" do
      uploader.cache!(File.open(file_path('test+.jpg')))
      is_expected.to eq("/uploads/tmp/#{cache_id}/test%2B.jpg")
    end

    context "with a cached file" do
      before { uploader.cache!(test_file) }

      it "gets the directory relative to public, prepending a slash" do
        is_expected.to eq("/uploads/tmp/#{cache_id}/#{test_file_name}")
      end

      describe "File#url" do
        before do
          allow(uploader.file).to receive(:url).and_return(file_url)
        end

        context "when present" do
          let(:file_url) { 'http://www.example.com/someurl.jpg' }

          it { is_expected.to eq(file_url) }
        end

        context "when blank" do
          let(:file_url) { '' }

          it "returns the relative path" do
            is_expected.to eq("/uploads/tmp/#{cache_id}/#{test_file_name}")
          end
        end
      end
    end

    context "File#url" do
      let(:file_class) { Class.new }
      let(:file) { file_class.new }

      before do
        allow(uploader).to receive(:file).and_return(file)
      end

      it "does not accept arguments" do
        file.define_singleton_method(:url) { true }
        uploader.url
      end

      it "does accept arguments" do
        file.define_singleton_method(:url) { |x = true| x }
        expect(file).to receive(:url).once.and_call_original
        uploader.url
      end
    end

    describe "(:thumb)" do
      subject { uploader.url(:thumb) }

      it "raises ArgumentError when version doesn't exist" do
        expect { uploader.url(:thumb) }.to raise_error(ArgumentError)
      end

      context "when version is specified" do
        before do
          MyCoolUploader.version(:thumb)
          uploader.cache!(test_file)
        end

        it "doesn't raise ArgumentError when versions version exists" do
          expect { uploader.url(:thumb) }.not_to raise_error
        end

        it "gets the directory relative to public for a specific version" do
          is_expected.to eq("/uploads/tmp/#{cache_id}/thumb_#{test_file_name}")
        end

        describe "asset_host" do
          before { uploader.class.configure { |config| config.asset_host = asset_host } }

          context "when set as a string" do
            let(:asset_host) { "http://foo.bar" }

            it "prepends the string" do
              is_expected.to eq("#{asset_host}/uploads/tmp/#{cache_id}/thumb_#{test_file_name}")
            end

            describe "encoding" do
              let(:test_file_name) { 'test+.jpg' }

              it "encodes the path of a file" do
                is_expected.to eq("#{asset_host}/uploads/tmp/#{cache_id}/thumb_test%2B.jpg")
              end

              it "double-encodes the path of an available File#url" do
                url = 'http://www.example.com/directory%2Bname/another%2Bdirectory/some%2Burl.jpg'
                allow(uploader.file).to receive(:url).and_return(url)

                expect(uploader.url).to eq(url)
              end
            end
          end

          context "when set as a proc" do
            let(:asset_host) { proc { "http://foo.bar" } }

            it "prepends the result of proc" do
              is_expected.to eq("#{asset_host.call}/uploads/tmp/#{cache_id}/thumb_#{test_file_name}")
            end

            describe "encoding" do
              let(:test_file_name) { 'test+.jpg' }

              it { is_expected.to eq("#{asset_host.call}/uploads/tmp/#{cache_id}/thumb_test%2B.jpg") }
            end
          end

          context "when set as nil" do
            let(:asset_host) { nil }

            context "when base_path is set" do
              let(:base_path) { "/base_path" }

              before do
                uploader.class.configure do |config|
                  config.base_path = base_path
                end
              end

              it "prepends the config option 'base_path'" do
                is_expected.to eq("#{base_path}/uploads/tmp/#{cache_id}/thumb_#{test_file_name}")
              end
            end
          end
        end
      end

      context "when the version is nested" do
        subject { uploader.url(:thumb, :mini) }

        before do
          MyCoolUploader.version(:thumb) { version(:mini) }
          uploader.cache!(test_file)
        end

        it "gets the directory relative to public for a nested version" do
          is_expected.to eq("/uploads/tmp/#{cache_id}/thumb_mini_#{test_file_name}")
        end
      end
    end
  end

  describe '#to_json' do
    subject(:parsed_json) { JSON.parse(to_json) }

    let(:to_json) { uploader.to_json }

    context "(:thumb)" do
      before { MyCoolUploader.version(:thumb) }

      it { expect(parsed_json.keys).to include("url") }
      it { expect(parsed_json.keys).to include("thumb") }
      it { expect(parsed_json["url"]).to be_nil }
      it { expect(parsed_json["thumb"].keys).to include("url") }
      it { expect(parsed_json["thumb"]["url"]).to be_nil }

      context "with a cached_file" do
        before { uploader.cache!(test_file) }

        it { expect(parsed_json.keys).to include("thumb") }
        it { expect(parsed_json["thumb"]).to eq({"url" => "/uploads/tmp/#{cache_id}/thumb_#{test_file_name}"}) }
      end
    end

    context "with cached file" do
      before { uploader.cache!(test_file) }

      it "returns a hash including a cached URL" do
        is_expected.to eq({"url" => "/uploads/tmp/#{cache_id}/#{test_file_name}"})
      end
    end

    it "allows an options parameter to be passed in" do
      expect { uploader.to_json({:some => 'options'}) }.not_to raise_error
    end
  end

  describe '#to_xml' do
    subject(:parsed_xml) { Hash.from_xml(to_xml) }

    let(:to_xml) { uploader.to_xml }

    it "returns a hash with a blank URL" do
      is_expected.to eq({"uploader" => {"url" => nil}})
    end

    context "with cached file" do
      before { uploader.cache!(test_file) }

      it "returns a hash including a cached URL" do
        is_expected.to eq({"uploader" => {"url" => "/uploads/tmp/#{cache_id}/#{test_file_name}"}})
      end

      context "with an array of uploaders" do
        let(:to_xml) { [uploader].to_xml }

        it "returns a hash including an array with a cached URL" do
          is_expected.to have_value([{"url"=>"/uploads/tmp/#{cache_id}/#{test_file_name}"}])
        end
      end
    end

    describe "(:thumb)" do
      before { MyCoolUploader.version(:thumb) }

      context "with cached file" do
        before { uploader.cache!(test_file) }

        it "returns a hash including a cached URL of a version" do
          expect(parsed_xml["uploader"]["thumb"]).to eq({"url" => "/uploads/tmp/#{cache_id}/thumb_#{test_file_name}"})
        end
      end
    end
  end

  describe '#to_s' do
    subject { uploader.to_s }

    it { is_expected.to eq('') }

    context "with cached file" do
      before { uploader.cache!(test_file) }

      it "gets the directory relative to public, prepending a slash" do
        is_expected.to eq("/uploads/tmp/#{cache_id}/#{test_file_name}")
      end

      describe "File#url" do
        before { allow(uploader.file).to receive(:url).and_return(url) }

        context "when present" do
          let(:url) { 'http://www.example.com/someurl.jpg' }

          it { is_expected.to eq(url) }
        end
      end
    end
  end
end