views:

60

answers:

1

Given the Thread class with it current method. Now inside a test, I want to do this:

def test_alter_current_thread
  Thread.current = a_stubbed_method
  # do something that involve the work of Thread.current
  Thread.current = default_thread_current
end

Basically, I want to alter the method of a class inside a test method and recover it after that. I know it sound complex for another language, like Java & C# (in Java, only powerful mock framework can do it). But it's ruby and I hope such nasty stuff would be available

+2  A: 

You might want to take a look at a Ruby mocking framework like Mocha, but in terms of using plain Ruby it can be done using alias_method (documentation here) e.g.

beforehand:

class Thread
  class << self
    alias_method :old_current, :current
  end
end

then define your new method

class Thread
  def self.current
    # implementation here
  end
end

then afterwards restore the old method:

class Thread
  class << self
    alias_method :current, :old_current
  end
end

Update to illustrate doing this from within a test

If you want to do this from within a test you could define some helper methods as follows:

def replace_class_method(cls, meth, new_impl)
  cls.class_eval("class << self; alias_method :old_#{meth}, :#{meth}; end")
  cls.class_eval(new_impl)
end

def restore_class_method(cls, meth)
  cls.class_eval("class << self; alias_method :#{meth}, :old_#{meth}; end")
end

replace_class_method is expecting a class constant, the name of a class method and the new method definition as a string. restore_class_method takes the class and the method name and then aliases the original method back in place.

Your test would then be along the lines of:

def test
  new_impl = <<EOM
  def self.current
    "replaced!"
  end
EOM
  replace_class_method(Thread, 'current', s)
  puts "Replaced method call: #{Thread.current}"
  restore_class_method(Thread, 'current')
  puts "Restored method call: #{Thread.current}"
end

You could also write a little wrapper method which would replace a method, yield to a block and then ensure that the original method was reinstated afterwards e.g.

def with_replaced_method(cls, meth, new_impl)
    replace_class_method(cls, meth, new_impl)
    begin
        result = yield
    ensure
        restore_class_method(cls, meth)
    end
    return result
end

Inside your test method this could then be used as:

with_replaced_method(Thread, 'current', new_impl) do
  # test code using the replaced method goes here
end
# after this point the original method definition is restored

As mentioned in the original answer, you can probably find a framework to do this for you but hopefully the above code is interesting and useful anyway.

mikej
Can I put these declarations in the middle of a test method?
Phương Nguyễn
Answer updated with example code for use within the middle of a test method. Let me know if any of it needs a more detailed explanation.
mikej
Thanks for the solution. Your code is cool. But since you are trying to `eval` the method from string, I cannot inject a local object into that. I turned to Mocha and stuff become much easier.
Phương Nguyễn
Cool. Am glad that you're having success with Mocha. It's also worth looking at what you can do with `define_method` instead of using an `eval` as that might let you inject a local object.
mikej
Thanks for the tip. I will try.
Phương Nguyễn