require 'class_constants'

class Class
   def mock(klass, method_name, new_method_body)
     @mocked_classes ||= []
     @mocked_classes << klass
     mock_class = Class.new(klass)
     mock_class.class_eval(%Q{
         def #{method_name.to_s}(*args)
           #{new_method_body}
         end})
     set_constant(klass.name, mock_class)
  end

   def unmock
     @mocked_classes.each do |klass|
       set_constant(klass.name, klass)
     end
  end
end

unit_test do
  class Engine
    attr_reader :num_of_cylinders

    def initialize(num_of_cylinders)
      @num_of_cylinders = num_of_cylinders
    end

    def speed
      44
    end
  end

  class Car
    def initialize
      @engine = Engine.new(6)
    end

    def speed
      @engine.speed
    end

    def num_of_cylinders
      @engine.num_of_cylinders
    end
  end

  class ClassMockTest < Test::Unit::TestCase
    def test_mocking
      Car.mock(Engine, :speed, 24)
      assert_equal(24, Car.new.speed)
      assert_equal(6, Car.new.num_of_cylinders)
      assert_equal(44, Engine.new(4).speed) # only effects references inside the class 'Car'
      Car.unmock
    end

    def test_unmocking
      Car.mock(Engine, :speed, 24)
      assert_equal(24, Car.new.speed)
      Car.unmock
      assert_equal(44, Car.new.speed)
    end
  end
end


#
# class_constants.rb
#

class Class
  def get_constant(constant_name)
       namespace_segments(constant_name).inject(self) { |scope, name| scope.const_get(name) }
  end

  def set_constant(constant_name, value)
    namespaces = namespace_segments(constant_name)
    if namespaces.size > 1
      context = get_constant(namespaces[0..-2].join) 
      context.const_set(namespaces[-1], value)
    else
      self.const_set(constant_name, value)
    end
  end

  def namespace_segments(name)
     name.sub(/^::/,'').split("::")
  end
end


unit_test do

  module A
    class One
    end
  end

  class Two
    def self.value
      "the number two"
    end
  end

  class Three
    def self.value
      "the number three"
    end
  end


  class ClassConstantsTest < Test::Unit::TestCase
    def test_get_constant
      assert_equal(A::One, self.class.get_constant('A::One'))
    end

    def test_set_constant
      new_class = Class.new
      assert_nothing_raised{self.class.set_constant('A::One', new_class)}
      assert_equal(new_class, self.class.get_constant('A::One'))

      assert_nothing_raised{self.class.set_constant('Two', Three)}
      assert_equal(Three.value, self.class.get_constant('Two').value)
    end

    def test_const_set
      self.class.const_set('Const1', 1)
      assert_equal(1, Const1)

      class1 = Class.new
      class2 = Class.new 
      self.class.const_set('Const2', class1)
      assert_equal(class1, Const2)

      self.class.const_set('Const2', class2)
      assert_equal(class2, Const2)

      class3 = Class.new 
      self.class.const_set('Const3', class3)
      Const3.const_set('Const2', class1)
      assert_equal(class1, Const3::Const2)
      assert_equal(class2, Const2)
      assert_equal(class2, Const2.new.class)
      assert_equal(class1, Const3::Const2.new.class)
      Const3.const_set('Const2', class2)
      assert_equal(class2, Const3::Const2.new.class)
    end

    module Module1
      class Class1
      end
    end

    class Class2
      attr_reader :var1
      def initialize
        @var1 = Module1::Class1.new
      end
    end

    class Class1a
    end

    def test_constant_resolution
      original_class1 = Module1::Class1
      assert_equal(Module1::Class1, Class2.new.var1.class)
      Module1.const_set('Class1', Class1a)
      assert_equal(Class1a, Class2.new.var1.class)
      assert_equal("ClassConstantsTest::Module1::Class1", original_class1.name)

      Module1.const_set('Class1', original_class1)
      assert_equal(Module1::Class1, Class2.new.var1.class)

      class1b = Class.new 
      Module1.const_set('Class1', class1b)
      assert_equal(class1b, Class2.new.var1.class)
      assert_equal("ClassConstantsTest::Module1::Class1", class1b.name)
      assert_equal("ClassConstantsTest::Module1::Class1", original_class1.name)
    end
  end
end